I have a User class in SQLAlchemy. I want to be able to encrypt the user's email address attribute in the database but still make it searchable through the filter query.
My problem is that if I use @hybrid_property my query theoretically works, but my construction doesn't, and if I use @property my construction works but my query doesn't
from cryptography.fernet import Fernet  # <- pip install cryptography
from werkzeug.security import generate_password_hash
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email_hash = db.Column(db.String(184), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    # @property       # <- Consider this as option 2...
    @hybrid_property  # <- Consider this as option 1...
    def email(self):
        f = Fernet('SOME_ENC_KEY')
        value = f.decrypt(self.email_hash.encode('utf-8'))
        return value
    @email.setter
    def email(self, email):
        f = Fernet('SOME_ENC_KEY')
        self.email_hash = f.encrypt(email.encode('utf-8'))
    @property
    def password(self):
        raise AttributeError('password is not a readable attribute.')
    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)
    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        # other checks and modifiers
For option 1: When I attempt to construct a user with User(email='a@example.com',password='secret') I receive the traceback,
~/models.py in __init__(self, **kwargs)
    431     # Established role assignment by default class initiation
    432     def __init__(self, **kwargs):
--> 433         super(User, self).__init__(**kwargs)
    434         if self.role is None:
    435             _default_role = Role.query.filter_by(default=True).first()
~/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py in _declarative_constructor(self, **kwargs)
    697             raise TypeError(
    698                 "%r is an invalid keyword argument for %s" %
--> 699                 (k, cls_.__name__))
    700         setattr(self, k, kwargs[k])
    701 _declarative_constructor.__name__ = '__init__'
TypeError: 'email' is an invalid keyword argument for User
For option 2: If instead I change @hybrid_property to @property the construction is fine but then my query User.query.filter_by(email=form.email.data.lower()).first() fails and returns None.
What should I change to get it working as required?
==============
Note I should say that I have tried to avoid using dual attributes since I didn't want to make extensive edits to the underlying codebase. so I have explicitly tried to avoid separating creation with querying in terms of User(email_input='a@a.com', password='secret') and User.query.filter_by(email='a@a.com').first():
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email_hash = db.Column(db.String(184), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    @hybrid_property
    def email(self):
        f = Fernet('SOME_ENC_KEY')
        value = f.decrypt(self.email_hash.encode('utf-8'))
        return value
    @property
    def email_input(self):
        raise AttributeError('email_input is not a readable attribute.')
    @email_input.setter
    def email_input(self, email):
        f = Fernet('SOME_ENC_KEY')
        self.email_hash = f.encrypt(email.encode('utf-8'))
    @property
    def password(self):
        raise AttributeError('password is not a readable attribute.')
    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)
    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        # other checks and modifiers
 
     
    