It isn't clear from the documentation how does the "collection_class" attribute work in a many-to-many relationship

Issue #1356 resolved
Former user created an issue

From the documentation on the site, it isn't clear how to create a many-to-many relation where both sides hold their relations in a user defined "collection_class". The doc doesn't mention if the "collection_class" attribute affects only one side of the relation or both.

For example, let's look on the following code:

import sqlalchemy
from sqlalchemy import *
from sqlalchemy.orm import *
engine = create_engine('sqlite://')
metadata = MetaData()
metadata.bind = engine

Session = scoped_session(sessionmaker(bind = engine, autoflush = True,
autocommit = False))

# TABLES

owners_table = Table('owners', metadata, 
    Column('owner_id', Integer, primary_key=True),
)

dogs_table = Table('dogs', metadata, 
    Column('dog_id', Integer, primary_key=True),
)

owners2dogs_table = Table('owners2dogs', metadata, 
    Column('owner_id', Integer, ForeignKey(owners_table.columns.owner_id)),
    Column('dog_id', Integer, ForeignKey(dogs_table.columns.dog_id)))

metadata.create_all()

# POJOS
class Owner(object):
    pass

class Dog(object):
    pass

# MAPPERS
owners2dogs_relation = relation(Dog,
                              backref = "owners",
                              secondary = owners2dogs_table,
                              collection_class = set)
mapper(Owner, owners_table, properties = {"dogs" : owners2dogs_relation})
mapper(Dog, dogs_table)

owner = Owner()
dog = Dog()
print type(owner.dogs)
print type(dog.owners)

The printed result is:

<class 'sqlalchemy.orm.collections.InstrumentedSet'>
<class 'sqlalchemy.orm.collections.InstrumentedList'>

and not:

<class 'sqlalchemy.orm.collections.InstrumentedSet'>
<class 'sqlalchemy.orm.collections.InstrumentedSet'>

I am not sure what is the anticipated result for most users. I can say for myself, that I thought that both sides would turn out to be a set, and I was wrong. Either way, it isn't mentioned anywhere - not in the site documentation and not in the code documentation.

The only way I could think of to make both sides keep their relation in a set is to do something like:

owners2dogs_relation = relation(Dog,
                              back_populates = "owners",
                              secondary = owners2dogs_table,
                              collection_class = set)
mapper(Owner, owners_table, properties = {"dogs" : owners2dogs_relation})
dogs2owners_relation = relation(Owner,
                              back_populates = "dogs",
                              secondary = owners2dogs_table,
                              collection_class = set)
mapper(Dog, dogs_table, properties = {"owners" : dogs2owners_relation})

If it's the answer, it should be documented too.

kobipe3@gmail.com

Comments (8)

  1. Mike Bayer repo owner

    whats wrong with

    owners2dogs_relation = relation(Dog,
                                  backref = backref("owners", collection_class=set),
                                  secondary = owners2dogs_table,
                                  collection_class = set)
    

    ?

    backref() as a function is in the ORM tutorial and the API reference, and is hyperlinked in the description of relation() too in the description of the backref keyword argument. its not nearly as obscure as "back_populates" so its funny you found that.

  2. Mike Bayer repo owner

    i think the docstring for backref() should include a list of what attributes are copied over from the parent property automatically. thats something we should clarify.

  3. Former user Account Deleted

    I'm so embarressed :) It's probably because most (or all, I'm not sure) of the examples containing a backref, didn't contain any other arguments for the backref function, other than the relation name. That's why I didn't even bother in checking the backref options. (and moved to check relation options instead) So my solution - put more and richer backref examples in the orm tutorial.

    And I still like my workaround. It makes all the mapping more symmetrical.

  4. Mike Bayer repo owner

    no access on the repo here and this ticket is very old; I think the current description of backref() is fine, people seem to get it.

  5. Log in to comment