Hi,

I've encountered a situation where I don't know if this is a bug or it is
the correct behavior. It is about association_proxy. Please see the
User.__init__ and Department.__init__ methods.

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy


Base = declarative_base()

DBSession = scoped_session(
    sessionmaker(
        extension=None
    )
)


class Entity(Base):
    __tablename__ = 'Entities'

    id = Column(Integer, primary_key=True)
    entity_type = Column(String(128), nullable=False)

    __mapper_args__ = {
        "polymorphic_on": entity_type,
        "polymorphic_identity": "SimpleEntity"
    }


class User(Entity):
    __tablename__ = 'Users'
    __mapper_args__ = {"polymorphic_identity": "User"}
    user_id = Column('id', Integer, ForeignKey('Entities.id'),
                     primary_key=True)

    departments = association_proxy(
        'department_role',
        'department',
        creator=lambda d: DepartmentUsers(department=d)
    )

    department_role = relationship(
        'DepartmentUser',
        back_populates='user',
        cascade='all, delete-orphan',
        primaryjoin='Users.c.id==Department_Users.c.uid'
    )

    def __init__(self, departments=None):
        super(User, self).__init__()
        if departments is None:
            departments = []

        # using this creates an auto flush and raises an IntegrityError
        # self.departments = departments

        # where as using this is OK
        for department in departments:
            self.department_role.append(
                DepartmentUser(user=self, department=department)
            )


class Department(Entity):
    __tablename__ = 'Departments'
    __mapper_args__ = {"polymorphic_identity": "Department"}
    department_id = Column('id', Integer, ForeignKey('Entities.id'),
                           primary_key=True)

    users = association_proxy(
        'user_role',
        'user',
        creator=lambda u: DepartmentUser(user=u)
    )

    user_role = relationship(
        "DepartmentUser",
        back_populates="department",
        cascade='all, delete-orphan',
        primaryjoin='Departments.c.id==Department_Users.c.did'
    )

    @validates('user_role')
    def _validate_user_role(self, key, user_role):
        """validates the given user_role variable
        """
        return user_role

    def __init__(self, users=None):
        if users is None:
            users = []
        # again using this creates an auto flush and raises an IntegrityError
        # self.users = users

        # where as using this is OK
        for user in users:
            self.user_role.append(
                DepartmentUser(department=self, user=user)
            )


class Role(Entity):
    __tablename__ = 'Roles'
    __mapper_args__ = {"polymorphic_identity": "Role"}
    role_id = Column('id', Integer, ForeignKey('Entities.id'), primary_key=True)


class DepartmentUser(Base):
    __tablename__ = 'Department_Users'

    user_id = Column(
        'uid',
        Integer,
        ForeignKey('Users.id'),
        primary_key=True
    )

    user = relationship(
        'User',
        back_populates='department_role',
        primaryjoin='DepartmentUser.user_id==User.user_id'
    )

    department_id = Column(
        'did',
        Integer,
        ForeignKey('Departments.id'),
        primary_key=True
    )

    department = relationship(
        'Department',
        back_populates='user_role',
        primaryjoin='DepartmentUser.department_id==Department.department_id'
    )

    role_id = Column(
        'rid',
        Integer,
        ForeignKey('Roles.id'),
        nullable=True
    )

    role = relationship(
        'Role',
        primaryjoin='DepartmentUser.role_id==Role.role_id'
    )

    def __init__(self, department=None, user=None, role=None):
        self.department = department
        self.user = user
        self.role = role


if __name__ == "__main__":
    database_engine_settings = {
        "sqlalchemy.url": "sqlite:///:memory:",
        "sqlalchemy.echo": False,
    }

    engine = engine_from_config(database_engine_settings, 'sqlalchemy.')
    DBSession.configure(
        bind=engine,
        extension=None
    )
    Base.metadata.create_all(engine)

    user1 = User()
    dep1 = Department()
    DBSession.add(user1)
    DBSession.add(dep1)
    DBSession.commit()

    dep1.users.append(user1)

    assert user1 in dep1.users
    assert dep1 in user1.departments

    dep_users = DBSession.query(DepartmentUser).all()
    assert len(dep_users) == 1

    user2 = User()
    dep1.users.append(user2)
    DBSession.add(user2)
    DBSession.commit()

So, I get an IntegrityError if I set the **self.departments** or
**self.users** directly, so apparently creating the association objects
triggers an auto-flush but if I create the association objects instead of
directly setting the attributes there is no error. I'm ready to use the
work around of creating the association object but I'm also curious if this
is a bug or normal way of using association_proxy.

Thanks,

Erkan Ozgur Yilmaz

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

Reply via email to