Unexpected TypeError when using `choices` as parameter name to TypeDecorator

29 views
Skip to first unread message

Steven James

unread,
May 6, 2021, 11:08:59 AM5/6/21
to sqlalchemy
This one is baffling me. The following example throws an exception about an unhashable type, related (I think) to the new query caching in 1.4. However, if I change the parameter name to AltType.__init__ to ANYTHING other than `choices`, it works fine. I can't find any reference to the name `choices` in the sqlalchemy codebase.

So why can't I use `choices` as a parameter name here? The answer might be related to the AnnotatedColumn in the generated expression but that's about as far as I've gotten.

```
# sqlalchemy 1.4.x

from sqlalchemy import Column, Integer, create_engine, TypeDecorator, Unicode
from sqlalchemy.orm import sessionmaker, as_declarative, declared_attr


class AltType(TypeDecorator):
    impl = Unicode(255)

    def __init__(self, choices):
        self.choices = choices
        super(AltType, self).__init__()


@as_declarative()
class Base(object):
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    pk = Column(Integer, primary_key=True)


class MyClass(Base):
    d = Column(AltType(['a', 'list', 'here']))


if __name__ == '__main__':
    e = create_engine('sqlite://', echo=True)
    conn = e.connect()

    Base.metadata.create_all(e)

    s = sessionmaker(e)()
    q = s.query(MyClass).filter(MyClass.d == 'search_str')
    
    result = q.first()  # <---- error here
    print(result)

```

result:
```
Traceback (most recent call last):
  File "...\scratch_78.py", line 36, in <module>
    results = q.first()
  File "...\\lib\site-packages\sqlalchemy\orm\query.py", line 2750, in first
    return self.limit(1)._iter().first()
  File "...\\lib\site-packages\sqlalchemy\orm\query.py", line 2834, in _iter
    result = self.session.execute(
  File "...\\lib\site-packages\sqlalchemy\orm\session.py", line 1675, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "...\\lib\site-packages\sqlalchemy\engine\base.py", line 1521, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "...\\lib\site-packages\sqlalchemy\sql\elements.py", line 313, in _execute_on_connection
    return connection._execute_clauseelement(
  File "...\\lib\site-packages\sqlalchemy\engine\base.py", line 1382, in _execute_clauseelement
    compiled_sql, extracted_params, cache_hit = elem._compile_w_cache(
  File "...\\lib\site-packages\sqlalchemy\sql\elements.py", line 531, in _compile_w_cache
    compiled_sql = compiled_cache.get(key)
  File "...\\lib\site-packages\sqlalchemy\util\_collections.py", line 918, in get
    item = dict.get(self, key, default)
TypeError: unhashable type: 'list'
```

-Steven James

Mike Bayer

unread,
May 6, 2021, 11:46:53 AM5/6/21
to noreply-spamdigest via sqlalchemy
this is a regression in SQLAlchemy in that the TypeDecorator is being applied to caching without giving the end user any clue or API to control this process.

the name "choices" is not significant, it's that the parameter as given is trying to be part of a cache key, so turn this into a tuple for now to resolve:


class AltType(TypeDecorator):
    impl = Unicode(255)

    def __init__(self, choices):
        self.choices = tuple(choices)
        super(AltType, self).__init__()


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
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+...@googlegroups.com.

Steven James

unread,
May 6, 2021, 11:52:51 AM5/6/21
to sqlalchemy
I originally came to that conclusion, and I agree that replacing it with a tuple does fix it, but I still can't explain why using a different parameter name also fixes it.

Mike Bayer

unread,
May 6, 2021, 11:59:14 AM5/6/21
to noreply-spamdigest via sqlalchemy
the caching routine for datatypes is matching up the names of the parameters to the internal state of the object.   any name will reproduce as long as you name the parameters and internal attributes the same.

Steven James

unread,
May 6, 2021, 12:06:38 PM5/6/21
to sqlalchemy
Thank you! That makes me feel quite a bit better.
Reply all
Reply to author
Forward
0 new messages