kvesteri / sqlalchemy-utils

Various utility functions and datatypes for SQLAlchemy.
Other
1.26k stars 321 forks source link

Documentation issue with automatic data coercion #99

Open sirex opened 9 years ago

sirex commented 9 years ago

Today I spent almost 2 hours while finally figured out, why this simple example does not work:

>>> target = Model()
>>> target.password = 'b'
>>> target.password.hash
'$5$rounds=80000$H.............'
>>> target.password == 'b'
True

To make it work, I had to add this:

from sqlalchemy_utils import force_auto_coercion
force_auto_coercion()

Documentation has a section about automatic data coercion, but it is not clear in what cases I need to use it, and documentation for password field does not tell, that I have to force auto coercion in order for password field functionality to work.

I think, this bit about auto coercion should be added to install section and a note should be added for all fields that depends on auto coercion.

fuhrysteve commented 9 years ago

force_auto_coercion is probably not the right way to do this. For most cases, you probably just want session.flush(), i.e.:

>>> target = Model()
>>> target.password = 'b'
>>> session.add(target)
>>> session.flush()
>>> target.password.hash
'$5$rounds=80000$H.............'
>>> target.password == 'b'
True
sirex commented 9 years ago

I tried flush, but it didn't work.

Here is script that proves it:

from setuptools.dist import Distribution

Distribution().fetch_build_eggs([
    'sqlalchemy==0.9.8',
    'sqlalchemy-utils==0.27.7',
    'passlib==1.6.2',
])

import sqlalchemy as sa

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import PasswordType

engine = create_engine('sqlite:///:memory:')
connection = engine.connect()
Base = declarative_base()
Session = sessionmaker(bind=connection)
session = Session()

# Test without force_auto_coercion
##################################

class Model(Base):
    __tablename__ = 'user'
    id = sa.Column(sa.Integer, primary_key=True)
    password = sa.Column(PasswordType(schemes=['pbkdf2_sha512']))

Base.metadata.create_all(connection)

target = Model()
target.password = 'b'
session.add(target)
session.flush()
assert target.password.hash is None
assert target.password != 'b'

# Test with force_auto_coercion
###############################

from sqlalchemy_utils import force_auto_coercion

force_auto_coercion()

class Model2(Base):
    __tablename__ = 'user2'
    id = sa.Column(sa.Integer, primary_key=True)
    password = sa.Column(PasswordType(schemes=['pbkdf2_sha512']))

Base.metadata.create_all(connection)

target = Model2()
target.password = 'b'
assert target.password.hash.startswith('$pbkdf2-sha512$')
assert target.password == 'b'
fuhrysteve commented 9 years ago

Interesting.. yeah I've noticed similar behavior with some other types now that I think more about it.

Looks like the docs are relatively sparse about it.

olegpidsadnyi commented 7 years ago

Any plans to fix it to work with flush?