Flask AttributeError:无法设置属性

时间:2018-10-09 15:43:16

标签: python flask sqlalchemy

当我使用sqlite创建数据库时,我正在python烧瓶上创建一个项目,并创建了数据库并在其中插入了值,但是当我重新启动项目并尝试通过python控制台(例如)将值插入数据库时​​,

from app import db
from app import Credentials as C
c= C(fname="John", lname="Doe", username="johndoe", email="john@doe.com", password="11111111")

我遇到这样的错误

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<string>", line 4, in __init__
  File "C:\Users\Lenovo\PycharmProjects\FlaskProject\venv\lib\site-packages\sqlalchemy\orm\state.py", line 417, in _initialize_instance
    manager.dispatch.init_failure(self, args, kwargs)
  File "C:\Users\Lenovo\PycharmProjects\FlaskProject\venv\lib\site-packages\sqlalchemy\util\langhelpers.py", line 66, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "C:\Users\Lenovo\PycharmProjects\FlaskProject\venv\lib\site-packages\sqlalchemy\util\compat.py", line 249, in reraise
    raise value
  File "C:\Users\Lenovo\PycharmProjects\FlaskProject\venv\lib\site-packages\sqlalchemy\orm\state.py", line 414, in _initialize_instance
    return manager.original_init(*mixed[1:], **kwargs)
  File "C:\Users\Lenovo\PycharmProjects\FlaskProject\venv\lib\site-packages\sqlalchemy\ext\declarative\base.py", line 700, in _declarative_constructor
    setattr(self, k, kwargs[k])
AttributeError: can't set attribute

帮助我解决使用所有最新版本库的错误

这是我的模型定义:

class Credentials(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    fname = db.Column(db.String(32), nullable=False)
    lname = db.Column(db.String(32))
    username = db.Column(db.String(16), unique=True, nullable=False)
    email = db.Column(db.String(64), unique=True, nullable=False)
    password = db.Column(db.String(100))
    date_created = db.Column(db.DateTime, default=datetime.utcnow)

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    def __repr__(self):
        return f"Users('{self.fname}','{self.email}','{self.username}')"

3 个答案:

答案 0 :(得分:2)

我今天也遇到了同样的问题。很可能是 SQLAlchemy 的问题,它是 flask-sqlalchemy 的依赖项。我比较了最近项目中使用的版本和当前版本,发现 SQLAlchemy==1.4.0 有更新。

为了检查版本,我做了:

$ pip3 freeze

为了恢复到以前的版本,我先卸载了当前的SQLAlchemy==1.4.0,然后重新安装了以前的版本SQLAlchemy==1.3.23

$ pip3 uninstall SQLAlchemy==1.4.0 # my current version
$ pip3 install SQLAlchemy==1.3.23 # previous working version

更新您的要求:

$ pip3 freeze > requirements.txt

答案 1 :(得分:1)

您的错误是由password属性定义引起的:

@property
def password(self):
    raise AttributeError('password is not a readable attribute')

已替换较早的名称password定义,带有没有设置器的属性对象。您可以使用

重现该问题
>>> class Foo:
...     @property
...     def bar(self):
...         raise AttributeError('bar is not a readable attribute')
...
>>> f = Foo()
>>> f.bar = 'new value'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

除非明确地给它一个属性,否则属性没有setter,并且属性只是类命名空间中的更多对象,列定义和属性不能使用相同的名称。

例如,您可以将列重命名为_password,但是这里存在一个更大的问题: 您永远不要在数据库中存储用户密码

相反,您应该存储密码哈希,该密码是通过密码生成的唯一字符串,只有在将来为您的程序提供相同的密码时,才可以复制该字符串。这样,您可以验证,即使有人设法窃取您的数据库,也不会让黑客访问密码,而仍然有人知道正确的密码。

非常重要的一点是,不要让黑客访问密码 。请参阅this analysis of a breach of security at Adobe,了解为什么您永远不想处于相同的情况。我的帐户也遭到了该黑客的入侵,但这仅仅是因为我在每个站点上使用唯一的密码,黑客才得以重新使用我的帐户信息来访问其他站点。

请参阅this guide,以获取有关应该执行的操作的良好概述。 Flask附带了正确的工具来为您执行此操作。

在这里使用werkzeug.security package应该足够了:

from sqlalchemy.ext.hybrid import hybrid_property
from werkzeug.security import generate_password_hash, check_password_hash

class Credentials(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    fname = db.Column(db.String(32), nullable=False)
    lname = db.Column(db.String(32))
    username = db.Column(db.String(16), unique=True, nullable=False)
    email = db.Column(db.String(64), unique=True, nullable=False)
    date_created = db.Column(db.DateTime, default=datetime.utcnow)

    _password_hash = db.Column('password', db.String(100))

    @hybrid_property
    def password(self):
        return self._password_hash

    @password.setter
    def _set_password(self, plaintext):
        self._password_hash = generate_password_hash(plaintext)

    def validate_password(self, password):
        return check_password_hash(self._password_hash, password)

    def __repr__(self):
        return f"Users('{self.fname}', '{self.email}', '{self.username}')"

我在这里使用SQLAlchemy hybrid property object处理password属性。请注意,存储在_password中(但数据库列仍称为password),并且在设置该值时会自动对其进行哈希处理。

然后您可以使用check_password()方法针对数据库存储的哈希来验证密码。

werkzeug.security实现可防止定时攻击(攻击者可以通过测量服务器验证密码所花费的时间来确定它们是否接近正确的密码,从而可以确定攻击的时间),但在支持旧版方面较弱协议。其他软件包,例如Flask-BCrypt,可让您配置更强大的策略。

答案 2 :(得分:0)

@Martijn Pieters在某种程度上是正确的(关于建立设置器来设置值),但是由于属性名称不同,他的方法将引发错误。有关详情,请参见下文。

无论使用@hybrid_property装饰器还是本机python对象的@property装饰器,都必须保留用于setter或getter方法的属性定义中使用的相同属性名称。

在您的情况下,def _set_password()应该为def password()。您的代码应如下所示:

 @hybrid_property
 def password(self):
     return self._password_hash

 @password.setter
 def password(self, plaintext):
     self._password_hash = generate_password_hash(plaintext)  

详细说明:https://github.com/sqlalchemy/sqlalchemy/issues/4332