Unhashable Type error when modifying SqlAlchemy Mo

2019-07-15 13:58发布

问题:

I am using the most basic flask app practically copied from documentations, and am receiving an extremely annoying error. I've been able to trace it down but don't know how to solve it. I know the problem arises when implementing flask-security, but the error comes from inside sqlalchemy. Any advice?

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
import logging
app = Flask(__name__)

app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' #

db = SQLAlchemy(app)

roles_users = db.Table('roles_users',
        db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
        db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))

class Role(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

    def __init__(self, name, desc):
        self.name = name 
        self.description = desc

    def __repr__(self):
        return '<Role: {}>'.format(str(self.name))

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255))
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def __repr__(self):
        return '<User: {}>'.format(str(self.username))

def main():
    db.create_all()
    adminRole = Role('Admin', 'Unrestricted')
    adminUser = User('admin', 'adminpassword')
    adminUser.roles = [adminRole]
    db.session.add(adminUser)
    db.session.commit()
    app.run(debug=True)

if __name__ == '__main__':
    LOG_FILENAME = 'test.log'
    logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
    logging.debug('This message should go to the log file')
    try:
       main()
    except:
       logging.exception('Got exception on main handler')
       raise

The above code works completely fine. The problem comes when using Flask-Security and subclassing RoleMixin (which adds the eq and ne functions to the model. Once the class becomes this:

class Role(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

    def __init__(self, name, desc):
        self.name = name 
        self.description = desc

    def __repr__(self):
        return '<Role: {}>'.format(str(self.name))


    def __eq__(self, other):
        return (self.name == other or
                self.name == getattr(other, 'name', None))

    def __ne__(self, other):
        return not self.__eq__(other)

I get the following error:

ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "test.py", line 66, in <module>
    main()
  File "test.py", line 53, in main
    adminUser.roles = [adminRole]
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\attributes.py", line 220, in __set__
    instance_dict(instance), value, None)
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\attributes.py", line 975, in set
    lambda adapter, i: adapter.adapt_like_to_iterable(i))
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\attributes.py", line 1010, in _set_iterable
    collections.bulk_replace(new_values, old_collection, new_collection)
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\collections.py", line 782, in bulk_replace
    constants = existing_idset.intersection(values or ())
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\util\_collections.py", line 592, in intersection
    result._members.update(self._working_set(members).intersection(other))
TypeError: unhashable type: 'Role'
DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "test.py", line 66, in <module>
    main()
  File "test.py", line 53, in main
    adminUser.roles = [adminRole]
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\attributes.py", line 220, in __set__
    instance_dict(instance), value, None)
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\attributes.py", line 975, in set
    lambda adapter, i: adapter.adapt_like_to_iterable(i))
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\attributes.py", line 1010, in _set_iterable
    collections.bulk_replace(new_values, old_collection, new_collection)
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\orm\collections.py", line 782, in bulk_replace
    constants = existing_idset.intersection(values or ())
  File "C:\Users\ramabodhi\Envs\test\lib\site-packages\sqlalchemy\util\_collections.py", line 592, in intersection
    result._members.update(self._working_set(members).intersection(other))
TypeError: unhashable type: 'Role'

I am on python 3.3 windows 7 and all my packages are up to date.

回答1:

I have a "solution" for this, but I want to make sure its a viable solution and not an unstable hack.

I realize that adding the ne and eq functions to the class requires the hash function to be added in order for the model to be hashable. So this is now working:

class Role(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

    def __init__(self, name, desc):
        self.name = name 
        self.description = desc

    def __repr__(self):
        return '<Role: {}>'.format(str(self.name))


    def __eq__(self, other):
        return (self.name == other or
                self.name == getattr(other, 'name', None))

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash(self.name)

Let me know if I've done this properly, thanks!