Using RethinkDB with Flask
Contents
Using RethinkDB with Flask¶
I have already written some notes on RethinkDB, and have been using it in a jukebox server project. I believe I came up with quite an elegant instantiation of RethinkDB client driver with Flask. It is similar to existing solution, but without the need of carting round global functions and obscene imports.
Table of Contents¶
Setup¶
First, we set up the project and get our requirements
python3 -m venv venv && source venv/bin/activate
pip install flask rethinkdb
using a directory structure
my_project/
- __init__.py
- database.py
requirements.txt
app.py
config.py
NB: this is not a good layout, but sufficient for this example. We create a configuration for Flask:
# config.py
class Config():
DEBUG = False
RDB_HOST = 'localhost'
RDB_PORT = '28015'
class Development(Config):
DEBUG = True # NB seems to be a bug with this not actually enabling debug mode
class Production(Config):
...
where we provide the host and port of RethinkDB.
app.py
content¶
In app.py
we initialize, as with any case, our Flask app
# app.py
from flask import Flask
# import from database module to setup database in app context
from my_project.database import init_app_database
app = Flask(__name__)
# use development configuration
app.config.from_object('config.Development')
# register blueprints and endpoints here
init_app_database(app)
if __name__ == '__main__':
app.run(debug=app.config["DEBUG"]) # fix for debug mode
All of this is fairly standard, with the exception of calling this init_app_database
function. We will implement this in the my_project/database.py
file:
Registering the database connection in the application context¶
It is best to have one database connection per request. The reasons for this is that connections can drop or timeout, can be pooled to be optimized, and in general can be quite fiddly – so it is best to let the database and the driver handle connection optimization, and we as server architects will just create a connection in a context where we need one.
We will do this by instantiating a connection only once one is needed, and have each connection ‘unique’ to the context in which it is used. I say unique in inverted commas, since under the hood this could be different, but we don’t care as implementers; we want a connection per request.
Flask has some handy registration functions to assist with this, such as being able to register a function with app.before_request()
which gets called before each request. We also use app.teardown_appcontext()
to register a function to be called when a request context is garbage collected.
Additionally, we use Flask’s g
variable, which is related to the Flask application context. It is sufficient to know that the lifetime of g
is the lifetime of the request.
# my_project/database.py
from flask import current_app, g
from rethinkdb import r
from rethinkdb.errors import RqlRuntimeError
def _connect_to_database():
""" establish and return a connection object """
return r.connect(
host=current_app.config["RDB_HOST"],
port=current_app.config["RDB_PORT"]
)
def get_db_conn():
""" creates and/or returns conn object stored in g._db_conn """
if '_db_conn' not in g:
g._db_conn = _connect_to_database()
return g._db_conn
def teardown_db(env):
""" teardown function to remove g._db_conn if exists """
conn = g.pop('_db_conn', None)
if conn is not None:
current_app.logger.info("Disconnecting from database.")
conn.close()
def register_db_to_context():
""" registers function to retrieve connection in g.get_conn """
g.get_conn = get_db_conn
def init_app_database(app):
""" application init to register database functions """
app.teardown_appcontext(teardown_db)
app.before_request(register_db_to_context)
The register_db_to_context()
function essentially makes g.get_conn
behave a little like a property.
Using the connection¶
With this setup, we can now at any point in our program, without any additional imports besides Flask’s g
and RethinkDB’s r
use this connection
r.table("some_table").run(g.get_conn())
Comments¶
My initial plan was to see if I could dynamically register g.conn
with @property
behaviour, and although it can be done dynamically, it cannot be done on instances without modifying __getattribute__
and the like. I figured using a function call for now is sufficient, but may try and develop a more elegant solution in the future which makes using RethinkDB, and others, more intuitive.