Bug with hybrid property ?
I use SQLALchemy ORM with a declarative base and SQLITE. I use hybrid properties to encrypt TOTP secrets as follows:
class FactorsTOTP(Model):
app_secret = 'foobar'
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
_key = Column('key', String, nullable=False)
@hybrid_property
def key(self):
cipher_key = urlsafe_b64encode(self.app_secret.ljust(32, b'0'))
f = Fernet(cipher_key)
return f.decrypt(self._key.encode('utf-8'))
@key.setter
def key(self, value):
cipher_key = urlsafe_b64encode(self.app_secret.ljust(32, b'0'))
f = Fernet(cipher_key)
self._key = f.encrypt(value.encode('utf-8')).decode('ascii')
My problem is, when creating a row: factors_totp = models.FactorsTOTP(key=totp_key)
I'm not sure why, but the key getter is being called after the setter and fails on self._key.encode('utf-8'). Simple Python operations work (self._key + 'suffix'), but not calling a function (bytes(self.key, encoding='utf-8) fails as well).
Traceback (most recent call last):
File "/Users/olemoign/.pyenv/versions/rta/bin/rta_add_admin", line 11, in <module>
load_entry_point('rta', 'console_scripts', 'rta_add_admin')()
File "/Users/olemoign/Developer/Parsys/rta/rta/scripts/add_admin.py", line 27, in main
password, totp_key = _command_line_add_user(env['request'], admin)
File "/Users/olemoign/Developer/Parsys/rta/rta/services/users.py", line 363, in _command_line_add_user
factors_totp = models.FactorsTOTP(key=totp_key)
File "<string>", line 4, in __init__
File "/Users/olemoign/.pyenv/versions/3.5.2/envs/rta/lib/python3.5/site-packages/sqlalchemy/orm/state.py", line 414, in _initialize_instance
manager.dispatch.init_failure(self, args, kwargs)
File "/Users/olemoign/.pyenv/versions/3.5.2/envs/rta/lib/python3.5/site-packages/sqlalchemy/util/langhelpers.py", line 66, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/Users/olemoign/.pyenv/versions/3.5.2/envs/rta/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 187, in reraise
raise value
File "/Users/olemoign/.pyenv/versions/3.5.2/envs/rta/lib/python3.5/site-packages/sqlalchemy/orm/state.py", line 411, in _initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "/Users/olemoign/.pyenv/versions/3.5.2/envs/rta/lib/python3.5/site-packages/sqlalchemy/ext/declarative/base.py", line 653, in _declarative_constructor
(k, cls_.__name__))
TypeError: 'key' is an invalid keyword argument for FactorsTOTP
This isn't so bad, as I can do
factors_totp = models.FactorsTOTP() factors_totp.key = totp_key
but I just wanted to raise the issue. Thank you for your great library.
Comments (3)
-
repo owner -
reporter Very well ! In the end, I'm mostly surprised that the getter is called during initialisation, but I guess that's linked to SQLAlchemy internals that make it possible to set the attributes at init in the first place.
Thanks for the super quick answer Michael !
-
repo owner - changed status to closed
yup, this is just the default constructor for declarative.init, you can add your own init to the class / base to override that happening
- Log in to comment
hello -
your hybrid needs to provide an
@expression
so that it works at the class level as well, e.g. one could say "FactorsOTP.key". This expression is failing right now because of some combination of "self.app_secret" doesn't exist and/or FactorsOTP._key is a column expression and has no method ".encode()".You may respond, "well my hybrid has no intention of being used as a SQL expression". Then this should not be a hybrid, use
@property
.no issue observed here...