Skip to content

ensure unhashable types work the same in 1.0 as in 0.9 and former #3416

@sqlalchemy-bot

Description

@sqlalchemy-bot

Migrated issue, originally created by Konsta Vesterinen (@kvesteri)

Before SA 1.0 I could have denormalized schemas where foreign keys pointed to unhashable types (let's say HSTORE). This was very common in same cases for me as I had for example denormalized translation columns using HSTORE as the underlying data type. Then foreign keys with onupdate='CASCADE' where used for automatic real-time denormalization when data changed. I created a simplified test case:

import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.postgresql import HSTORE


engine = sa.create_engine(
    'postgres://postgres@localhost/sqlalchemy_utils_test'
)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()

session.execute('CREATE EXTENSION IF NOT EXISTS hstore')
session.commit()


class Category(Base):
    __tablename__ = 'category'
    id = sa.Column(sa.Integer, primary_key=True)
    data = sa.Column(HSTORE)
    __table_args__ = (
        sa.Index(
            'some_index',
            data,
            id,
            unique=True
        ),
    )


class Article(Base):
    __tablename__ = 'article'
    id = sa.Column(sa.Integer, primary_key=True)

    name = sa.Column(sa.String)
    category_id = sa.Column(sa.Integer)
    category_data = sa.Column(HSTORE)
    category = sa.orm.relationship(Category)

    __table_args__ = (
        sa.ForeignKeyConstraint(
            [category_data, category_id],
            ['category.data', 'category.id'],
            onupdate='CASCADE'
        ),
    )


Base.metadata.create_all(bind=session.bind)


article = Article(name='Some article', category=Category(data={'1': '2'}))
session.add(article)
session.commit()
category = Category(data={'2': '2'})
session.commit()
article.category = category
session.commit()


Base.metadata.drop_all(bind=session.bind)

throws Exception

...
    elif orm_util._never_set.intersection(params.values()):
TypeError: unhashable type: 'dict'

The problem is even deeper than this as the following code block illustrates:

import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.postgresql import HSTORE


engine = sa.create_engine(
    'postgres://postgres@localhost/sqlalchemy_utils_test'
)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()

session.execute('CREATE EXTENSION IF NOT EXISTS hstore')
session.commit()


class Article(Base):
    __tablename__ = 'article'
    id = sa.Column(HSTORE, primary_key=True)


Base.metadata.create_all(bind=session.bind)


article = Article(id={'1': '2'})
session.add(article)
session.commit()


Base.metadata.drop_all(bind=session.bind)

Which throws exception:

Traceback (most recent call last):
  File "sa_test.py", line 42, in <module>
    session.commit()
  File ".../sqlalchemy/orm/session.py", line 790, in commit
    self.transaction.commit()
  File ".../sqlalchemy/orm/session.py", line 392, in commit
    self._prepare_impl()
  File ".../sqlalchemy/orm/session.py", line 372, in _prepare_impl
    self.session.flush()
  File ".../sqlalchemy/orm/session.py", line 2004, in flush
    self._flush(objects)
  File ".../sqlalchemy/orm/session.py", line 2122, in _flush
    transaction.rollback(_capture_exception=True)
  File ".../sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File ".../sqlalchemy/util/compat.py", line 182, in reraise
    raise value
  File ".../sqlalchemy/orm/session.py", line 2086, in _flush
    flush_context.execute()
  File ".../sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
  File ".../sqlalchemy/orm/unitofwork.py", line 532, in execute
    uow
  File ".../sqlalchemy/orm/persistence.py", line 149, in save_obj
    base_mapper, states, uowtransaction
  File ".../sqlalchemy/orm/persistence.py", line 292, in _organize_states_for_save
    instance_key in uowtransaction.session.identity_map:
  File ".../sqlalchemy/orm/identity.py", line 96, in __contains__
    if key in self._dict:
TypeError: unhashable type: 'dict'

The second issue is not critical for me but the first is. I have many projects using HSTORE based translations and denormalization.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingorm

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions