I would like to use Flask-Security with RethinkDB, but I cannot get my custom RethinkDBUserDatastore to work properly. Below is the code I've created for the custom data store, plus the adapted example app from the Flask-Security documentation.
rethinkdb_security.py
file is the implementation of the Flask-Security classes necessary to create a relevant datastore objects, based on the examples provided for SQLAlchemy and Mongo. Below is the current source code I'm using:
from flask_security import UserMixin, RoleMixin
from flask_security.datastore import Datastore
from flask_security.datastore import UserDatastore
import rethinkdb as r
class Bunch(object):
def __init__(self, obj, **kws):
self.__dict__.update(obj)
self.__dict__.update(kws)
def __repr__(self):
return 'Bunch({})'.format(repr(self.__dict__))
class User(Bunch, UserMixin):
def __init__(self, *args, **kwargs):
super(User, self).__init__(*args, **kwargs)
if not hasattr(self, 'active'):
self.active = True
if not hasattr(self, 'roles'):
self.roles = []
class Role(Bunch, RoleMixin):
def __init__(self, *args, **kwargs):
super(Role, self).__init__(*args, **kwargs)
class RethinkDBDatastore(Datastore):
def commit(self):
pass
def put(self, model):
return model
def delete(self, model):
pass
class RethinkDBUserDatastore(UserDatastore):
def __init__(self, db_conn, user_table='users', role_table='roles', user_pk='id', role_pk='id'):
self.db_conn = db_conn
self.user_table = user_table
self.role_table = role_table
self.user_pk = user_pk
self.role_pk = role_pk
RethinkDBDatastore.__init__(self, db=db_conn)
def commit(self):
pass
def get_user(self, id_or_email):
obj = r.table(self.user_table).get(id_or_email).run(self.db_conn)
return User(obj)
def find_user(self, **query):
return User(r.table(self.user_table).filter(query)[0].run(self.db_conn))
def find_role(self, **query):
return Role(r.table(self.role_table).filter(query)[0].run(self.db_conn))
def add_role_to_user(self, user, role):
return r.table(self.user_table) \
.get(user[self.user_pk]) \
.update({'roles': r.row['roles'].default([]).set_insert(role.name)}) \
.run(self.db_conn)
def remove_role_from_user(self, user, role):
return r.table(self.user_table) \
.get(user[self.user_pk]) \
.update({'roles': r.row['roles'].default([]).set_difference([role.name])}) \
.run(self.db_conn)
def toggle_active(self, user):
user['active'] = not user.get('active', True)
return True
def deactivate_user(self, user):
active = user.get('active', True)
if active:
user['active'] = False
return True
return False
def activate_user(self, user):
active = user.get('active', True)
if not active:
user['active'] = True
return True
return False
def create_role(self, **role):
result = r.table(self.role_table).insert(role, return_changes='always').run(self.db_conn)
return result['changes'][0]['new_val']
def create_user(self, **user):
if 'roles' in user:
user['roles'] = [role['name'] for role in roles]
if 'id' not in user:
user['id'] = user['email'] or user['username']
result = r.table(self.user_table).insert(user, return_changes='always').run(self.db_conn)
return result['changes'][0]['new_val']
def delete_user(self, user):
r.table(self.user_table).get(user[self.user_pk]).delete().run(self.db_conn)
flask_security_example.py
file is an example app I've created to test this datastore adapter. It runs, but throws an error when attempting to authenticate with the example user created in the create_user
function.
import bcrypt
import rethinkdb as r
from flask import Flask, render_template
from flask_security import Security, login_required
from rethinkdb import ReqlRuntimeError
from rethinkdb import ReqlOpFailedError
from rethinkdb_security import RethinkDBUserDatastore
# Get database connection information
DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_PORT = os.environ.get('DB_PORT', 28015)
DB_NAME = os.environ.get('DB_NAME', 'flask_security')
DB_TABLE = os.environ.get('DB_TABLE', 'users')
# Create app
APP = Flask(__name__)
APP.config['DEBUG'] = True
APP.config['SECRET_KEY'] = 'super-secret'
APP.config['SECURITY_PASSWORD_SALT'] = bcrypt.gensalt()
# Create the database connection
DB_CONN = r.connect(host=DB_HOST, port=DB_PORT, db=DB_NAME)
# Setup Flask-Security
USER_DATASTORE = RethinkDBUserDatastore(DB_CONN, user_table=DB_TABLE)
SECURITY = Security(APP, USER_DATASTORE)
# Create a user to test with
@APP.before_first_request
def create_user():
try:
r.db_create(DB_NAME).run(DB_CONN)
except ReqlRuntimeError:
pass
try:
r.db(DB_NAME).table_create(DB_TABLE).run(DB_CONN)
except ReqlOpFailedError:
pass
USER_DATASTORE.create_user(email='test@test.com', password='password')
# Views
@APP.route('/')
@login_required
def home():
return "Hello, world."
if __name__ == '__main__':
APP.run()
Is there something in particular that I'm doing wrong or some guidance that can be given as to how to create custom datastore adapters for Flask-Security?