Shuo Yang wrote:
>> I had in mind that if you then just said "add_property" like we're
>> doing now with circular relationships, it would overwrite the auto-
>> created one.
>
>
> In which case that the autogened property gets overwritten, table A that
> is
> back-referencing table B is still backreferencing table B implicitly or
> no?
>

Robert Leftwich wrote:
>
> Can I suggest that you make it more explicit, something like:
>
> Student.mapper = mapper(Student, studentTbl, properties={
>       'courses': two_way_relation(Course.mapper, enrolTbl, lazy=False)})
>
> or many_to_many_relation() or even just association().
>
> With the default that the related entity is the mapper that it is being
> added
> to, and allowing an explicit 'with=Admin.mapper' keyword to specify the
> related
> entity. This would allow any other 2-way relation specific properties to
> be
> specified w/o polluting the existing relation() interface, such as
> 'onChange' or
> more explicitly, 'onAppend=', 'onDelete', etc for the callbacks in
> TreeNode example.


OK, just as a note, we are not just talking about many-to-many
relationships, this applies to all relationships.  Like in the ZBlog app,
you have a Blog, which has a list of Posts.  But each Post also points to
its parent Blog.  Do I add the Post to the Blog, or set the Blog on the
Post to establish the relationship ?  same thing.

Also, we are talking about a solution that occurs totally externally to
the whole mapper/ORM module, with the exception of the interface that sets
it up (i.e. the 'relation' function).  The whole "magic" part of
SQLAlchemy is handled by a module called "attributes.py" which just adds
special properties to a class, so that scalar and list attributes can
store their histories and trigger events.  So we just add some extra
handling stuff that will automatically keep both sides of a two-way
relationship in sync.

At the very least, its going to need a class that defines this handling:

        class BackrefManager(object):
                        def __init__(self, key):
                                        self.key = key
                        def append(self, parent, child):
                                        pass
                        def delete(self, parent, child):
                                        pass

This object will probably need more functions to intercept operations on
slices, specific indexes, etc.

and heres the "List" version which does many to many:

        class ListBackrefManager(BackrefManager):
                def append(self, parent, child):
                        getattr(child, self.key).append(parent)

                def delete(self, parent, child):
                        del getattr(child, self.key)[parent]

and the one-to-many version:

        class OneToManyBackrefManager(BackrefManager):
                def append(self, parent, child):
                         setattr(child, self.key, parent)
                def delete(self, parent, child):
                         setattr(child, self.key, None)

So if we wanted to use these explicitly to fix our Course/Student problem,
it looks like this:

        Course.mapper = mapper(Course, courses)
        Student.mapper = mapper(Student, students, properties = {
                        'courses' : relation(Course.mapper, enrollment_table,
backreference=ListBackrefManager('students'))
        })

        Course.add_property('students', relation(Student.mapper,
                        enrollment_table, 
backreference=ListBackrefManager('courses'))


At the very least, im going to build this, because it solves the problem. 
Its just the un-pythonic/ugliness factor that has to be worked out.  Also
I think the many-to-many will by default always rely upon a straight
append() and delete...its not clear to me what should happen on the other
side of a relationship if you change the 4th item in the list, or
something like that.

So now onto interface.  First of all, I think this feature is going to be
wanted on most relationships.  Like in the ZBlog demo, I didnt think I
would use them everywhere, but it turns out every single relationship is
two way.  Even if you are not explicitly accessing the relationships in
both directions, its especially helpful for giving the objectstore
information on how to delete hierarchies of objects.

Example:  In ZBLog, I make a new Blog object.  I set its "user" attribute
to tell it which User it belongs to.  Then I make a new Post object.  I
set its "blog" attribute to tell it which Blog it belongs to.  The
application generally works in this direction, i.e. by setting "parents"
on objects.  When you commit new objects, everything works great.

But then, the administrator goes in and deletes that user.  SQLAlchemy
suddenly needs to know about all the relationships in reverse, else a
properly maintained database is going to have a ton of foreign key
violations.  So in ZBLog I have all the reverse relationships set up, so
that deletes cascade terrifically.  I also add that lame "live" keyword,
to insure that I can access the backreferences anytime without issue.

So I dont think the "backreference" idea is very special case at all, I
think it applies to all relations.

I have not gone down the road of making functions like "manytoone",
"onetomany", "foreignkey", "manytomany", etc.  When you make method names
like that, you are defining your relationships multiple times; once when
you set up all your Table objects, and then again when you set up your
mappers.  I like that the Table structures, in most cases, define what
relations actually occur.  Explicit method naming to define the specific
type of relationship is more appropriate for SQLObject since the table
metadata, relationships, and object-property mapping is set up all
monolithically within a single class definition, and it needs those
different method names to tell it what the table relationships are. 
SQLAlchemy knows how to look at your tables and decide what the
relationship naturally is.  Occasionally you might want to specify
"uselist=False" to force a one-to-one, and for a very small percentage of
objects you might want to use the primaryjoin/secondaryjoin options for
total customization.

Also, I was originally going to have the functions "eagerload" and
"lazyload", instead of "relation".  But in effect what you are doing is
just sending different names all down to the same object which is a
PropertyLoader (with two subclasses EagerLoader and LazyLoader).  I wanted
the grid of relationship options to formulate in such a way that "feels"
as open-ended as possible...its not clear to me if we take one of the
seven or eight options you can send to relation(), and turn it into a
method name, which ones they would be.  Would it be private_relation() ? 
eager_relation() ?  We can make method names like that...but then you get
that un-pythonic situation of, "do I use relation(lazy=True) or
lazy_relation()?"  One method that is configurable via keywords feels a
lot better to me.

So anyway, whatever naming scheme there is, we have to decide if the
"backreference" relation() is also set up for you.  I think in most cases,
users will want that relationship set up, and they arent going to need
much special on it.  Heres that case:

        Course.mapper = mapper(Course, courses)
        Student.mapper = mapper(Student, students, properties = {
                        'courses' : relation(Course.mapper, enrollment_table,
backreference='students')
        })

So above, the 'courses' relationship sets up the ListBackrefManager on the
Student object, and then makes a relation 'students' on the Course object
with a ListBackrefManager that listens to the 'courses' attribute on
Student.

Then the one more special case of, you set up a relation, you set up a
backreference, and you want to customize the backreference, i.e. with a
"selectalias" keyword or something like that (and i was thinking
"selectalias" should maybe be more automatic anyway...), So just do whats
natural:  add the property via add_property:


        Course.add_property('students', relation(Student.mapper,
                        enrollment_table, backreference='courses')

This sets up a new property for 'students' on Course, and a
ListBackrefManager that listens to the 'students' attribute on the Course
object.  When it goes to make the opposite relation, it sees one is
already there, and skips it, so theres no issue.

So thats my take on it at the moment.  I first need to just make the
BackrefManager classes and plug them in to see if this idea even works all
the way.




-------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc. Do you grep through log files
for problems?  Stop!  Download the new AJAX search engine that makes
searching your log files as easy as surfing the  web.  DOWNLOAD SPLUNK!
http://ads.osdn.com/?ad_id=7637&alloc_id=16865&op=click
_______________________________________________
Sqlalchemy-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users

Reply via email to