From 1846aeb7695c904a781c58b0981a8f5d2c0e1e0f Mon Sep 17 00:00:00 2001 From: Oleg Pidsadnyi Date: Wed, 16 Mar 2016 15:30:51 +0100 Subject: [PATCH] LazyCryptContext is used for the lazy configuration of the PasswordType. --- sqlalchemy_utils/types/password.py | 6 ++-- tests/types/test_password.py | 45 ++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/sqlalchemy_utils/types/password.py b/sqlalchemy_utils/types/password.py index 84d43f3..6202024 100644 --- a/sqlalchemy_utils/types/password.py +++ b/sqlalchemy_utils/types/password.py @@ -11,7 +11,7 @@ from .scalar_coercible import ScalarCoercible passlib = None try: import passlib - from passlib.context import CryptContext + from passlib.context import LazyCryptContext except ImportError: pass @@ -82,7 +82,7 @@ class PasswordType(types.TypeDecorator, ScalarCoercible): verifying them using a pythonic interface. All keyword arguments (aside from max_length) are forwarded to the - construction of a `passlib.context.CryptContext` object. + construction of a `passlib.context.LazyCryptContext` object. The following usage will create a password column that will automatically hash new passwords as `pbkdf2_sha512` but still compare @@ -128,7 +128,7 @@ class PasswordType(types.TypeDecorator, ScalarCoercible): ) # Construct the passlib crypt context. - self.context = CryptContext(**kwargs) + self.context = LazyCryptContext(**kwargs) if max_length is None: max_length = self.calculate_max_length() diff --git a/tests/types/test_password.py b/tests/types/test_password.py index e3c2d8a..641f5af 100644 --- a/tests/types/test_password.py +++ b/tests/types/test_password.py @@ -6,7 +6,13 @@ from sqlalchemy_utils import Password, PasswordType, types # noqa @pytest.fixture -def User(Base): +def extra_kwargs(): + """PasswordType extra keyword arguments.""" + return {} + + +@pytest.fixture +def User(Base, extra_kwargs): class User(Base): __tablename__ = 'user' id = sa.Column(sa.Integer, primary_key=True) @@ -17,8 +23,8 @@ def User(Base): 'md5_crypt', 'hex_md5' ], - - deprecated=['md5_crypt', 'hex_md5'] + deprecated=['md5_crypt', 'hex_md5'], + **extra_kwargs )) def __repr__(self): @@ -31,6 +37,17 @@ def init_models(User): pass +def onload_callback(schemes, deprecated): + """ + Get onload callback that takes the PasswordType arguments from the config. + """ + def onload(**kwargs): + kwargs['schemes'] = schemes + kwargs['deprecated'] = deprecated + return kwargs + return onload + + @pytest.mark.skipif('types.password.passlib is None') class TestPasswordType(object): @@ -178,3 +195,25 @@ class TestPasswordType(object): assert obj.password.hash.decode('utf8').startswith('$pbkdf2-sha512$') assert obj.password == 'b' + + @pytest.mark.parametrize( + 'extra_kwargs', + [ + dict( + onload=onload_callback( + schemes=['pbkdf2_sha256'], + deprecated=[], + ) + ) + ] + ) + def test_lazy_configuration(self, User): + """ + Field should be able to read the passlib attributes lazily from the + config (e.g. Flask config). + """ + schemes = User.password.type.context.schemes() + assert tuple(schemes) == ('pbkdf2_sha256',) + obj = User() + obj.password = b'b' + assert obj.password.hash.decode('utf8').startswith('$pbkdf2-sha256$')