1

I am making a project on python flask when I created a database with sqlite the database is created and the value is inserted in that but when I restarted my project and try to insert the values in the database through python console like

from app import db
from app import Credentials as C
c= C(fname="John", lname="Doe", username="johndoe", email="[email protected]", password="11111111")

I got an error like this

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

Help me to solve the error I am using all the latest versions of libraries

Here is my model definition:

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
  • 1
    The signup form and route have nothing to do with the error you see in the console, that only uses Credentials, which you haven't shared. Commented Oct 9, 2018 at 15:48
  • Also, please don't manually indent your code to make it look 'okay' in a post here. Paste your code, select the block of lines you want to format as 'code', and then use the {} button on the editor toolbar to format those lines in one step. Commented Oct 9, 2018 at 15:49
  • Sorry for that as I am new here, I'll take care of that next time. Commented Oct 9, 2018 at 15:53

3 Answers 3

3

Your error is caused by the password property definition:

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

This replaced the earlier definition for the name password with a property object without a setter. You can reproduce the issue with

>>> 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

Properties do not have setters unless you explicitly give it one, and properties are just more objects in the class namespace, you can't use the same name for a column definition and the property.

You could just rename the column to _password, for example, but there is a bigger issue here: You should never store user passwords in your database.

Instead, you should store password hashes, cryptographically generated unique strings that can only be reproduced when, in the future, your program is presented with the same password. That way you can verify that someone still knows the right password without giving hackers access to the passwords if they manage to steal your database.

It is very important that you never give hackers access to passwords. See this analysis of a breach of security at Adobe for why you would never want to be in the same situation. My account was compromised in that hack too, but only because I use unique passwords per site have hackers not been able to re-use my account information to access other sites.

See this guide for a good overview of what you should be doing instead. Flask comes with the right tools to do this for you.

Here it should be sufficient to use the 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}')"

I used the SQLAlchemy hybrid property object to handle the password attribute here. Note that the column is stored in _password (but the database column is still called password), and that the value is automatically hashed when you set it.

You can then validate passwords against the database-stored hash with the check_password() method.

The werkzeug.security implementation guards against timing attacks (where an attacker can work out if they are close to a proper password by measuring how long it takes your server to validate passwords), but is weaker in that it supports legacy protocols. Other packages such as Flask-BCrypt let you configure stronger policies.

Sign up to request clarification or add additional context in comments.

Comments

2

I have also encountered the same issue today. Most likely it is an issue with SQLAlchemy which is a dependancy of flask-sqlalchemy. I compared the version I used in a recent project and the current version and found out there is an update to SQLAlchemy==1.4.0.

To check the version, I did:

$ pip3 freeze

To revert to the previous version, I first uninstalled the current SQLAlchemy==1.4.0 and reinstalled the previous version SQLAlchemy==1.3.23

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

Update your requirements:

$ pip3 freeze > requirements.txt

Comments

0

@Martijn Pieters is right to some extend (about establishing a setter to set value) but his approach will throw an error due to different attribute name. See below for details.

Whether you use @hybrid_property decorator or a native python object's @property decorator you must retain same attribute name used in property defination for setter or getter methods.

Here in your case def _set_password() should be def password(). Your code should look like this:

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

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

Detail Explanation: https://github.com/sqlalchemy/sqlalchemy/issues/4332

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.