kvesteri / sqlalchemy-utils

Various utility functions and datatypes for SQLAlchemy.
Other
1.23k stars 317 forks source link

AttributeError on Polymorphic model with generic relationship #399

Open Jw-F opened 4 years ago

Jw-F commented 4 years ago

I have a Polymorphic model with a generic relationship. After expiring the instance, accessing attributes raises an AttributeError: columns.

To reproduce:

```Python from sqlalchemy import Column, Integer, Unicode, String, create_engine, MetaData from sqlalchemy_utils import generic_relationship from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker engine = create_engine('sqlite:///:memory:', echo=True) Session = sessionmaker(bind=engine) session = Session() meta = MetaData() BaseModel = declarative_base() class User(BaseModel): __tablename__ = 'user' id = Column(Integer, primary_key=True) class Message(BaseModel): __tablename__ = 'message' id = Column(Integer, primary_key=True) type = Column(String, nullable=False) creator_id = Column(Integer, nullable=False) creator_type = Column(Unicode(255), nullable=False) creator = generic_relationship(creator_type, creator_id) __mapper_args__ = { 'polymorphic_on': type } class Questions(Message): __mapper_args__ = { 'polymorphic_identity': 'question' } BaseModel.metadata.create_all(engine) user = User() session.add_all([user]) session.commit() question = Questions() question.creator = user question.channel = 'Test question' session.add(question) session.commit() session.expire_all() #This triggers the raise question.creator ```

Traceback:

``` Traceback (most recent call last): File "path/to/test_model.py", line 57, in question.creator File "/path/to/site-packages/sqlalchemy/orm/attributes.py", line 282, in __get__ return self.impl.get(instance_state(instance), dict_) File "/path/to/site-packages/sqlalchemy_utils/generic.py", line 32, in get discriminator = self.get_state_discriminator(state) File "/path/to/site-packages/sqlalchemy_utils/generic.py", line 51, in get_state_discriminator return state.attrs[discriminator.key].value File "/path/to/site-packages/sqlalchemy/orm/state.py", line 878, in value self.state.obj(), self.state.class_ File "/path/to/site-packages/sqlalchemy/orm/attributes.py", line 282, in __get__ return self.impl.get(instance_state(instance), dict_) File "/path/to/site-packages/sqlalchemy/orm/attributes.py", line 705, in get value = state._load_expired(state, passive) File "/path/to/site-packages/sqlalchemy/orm/state.py", line 660, in _load_expired self.manager.deferred_scalar_loader(self, toload) File "/path/to/site-packages/sqlalchemy/orm/loading.py", line 934, in load_scalar_attributes statement = mapper._optimized_get_statement(state, attribute_names) File "/path/to/site-packages/sqlalchemy/orm/mapper.py", line 2838, in _optimized_get_statement for c in props[key].columns File "/path/to/site-packages/sqlalchemy/util/langhelpers.py", line 949, in __getattr__ return self._fallback_getattr(key) File "/path/to/site-packages/sqlalchemy/util/langhelpers.py", line 923, in _fallback_getattr raise AttributeError(key) AttributeError: columns ```

Version info:

twelvelabs commented 4 years ago

I am running into the same issue: I have an STI model w/ a generic relationship defined on the base class. Subclasses are raising an attribute error post-save.

class Event(Base):
    __tablename__ = "events"

    actor_type: str = db.Column(db.Text)
    actor_id: int = db.Column(db.BigInteger, nullable=False)

    @declared_attr
    def actor(cls) -> Base:
        return generic_relationship("actor_type", "actor_id")

    subject_type: str = db.Column(db.Text)
    subject_id: int = db.Column(db.BigInteger, nullable=False)

    @declared_attr
    def subject(cls) -> Base:
        return generic_relationship("subject_type", "subject_id")

    type: str = db.Column(db.Text, nullable=False)

    __mapper_args__ = {
        "polymorphic_identity": "events",
        "polymorphic_on": type,
    }

class TestEventOne(Event):
    __mapper_args__ = {
        "polymorphic_identity": "test_event_one",
    }

I can create and persist a TestEventOne instance, but any attempt to access attributes raises the AttributeError mentioned above. I'm able to work around by manually refreshing the instance:

db.session.refresh(event)
dangayle commented 3 years ago

I just want to throw in a me too on this. The solution @twelvelabs showed above seems to work, but a fix for this would be great.