Skip to content

Instantly share code, notes, and snippets.

@catermelon
Created October 4, 2013 15:49
Show Gist options
  • Save catermelon/6828122 to your computer and use it in GitHub Desktop.
Save catermelon/6828122 to your computer and use it in GitHub Desktop.
Flask-SQLAlchemy - separating reads and writes
# This is not used unless SQLALCHEMY_BINDS is not present
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{username}:{password}@{hostname}/{database}?charset=utf8'
SQLALCHEMY_BINDS = {
'master': 'mysql+pymysql://{username}:{password}@{hostname}/{database}?charset=utf8',
'slave': 'mysql+pymysql://{username}:{password}@{hostname}/{database}?charset=utf8'
}
from flask.ext.sqlalchemy import SQLAlchemy, get_state
import sqlalchemy.orm as orm
from functools import partial
import logging
log = logging.getLogger(__name__)
class AutoRouteSession(orm.Session):
def __init__(self, db, autocommit=False, autoflush=False, **options):
self.app = db.get_app()
self._model_changes = {}
orm.Session.__init__(self, autocommit=autocommit, autoflush=autoflush,
extension=db.session_extensions,
bind=db.engine,
binds=db.get_binds(self.app), **options)
def get_bind(self, mapper=None, clause=None):
try:
state = get_state(self.app)
except (AssertionError, AttributeError, TypeError) as err:
log.info("Unable to get Flask-SQLAlchemy configuration. Outputting default bind. Error:" + err)
return orm.Session.get_bind(self, mapper, clause)
# If there are no binds configured, connect using the default SQLALCHEMY_DATABASE_URI
if state is None or not self.app.config['SQLALCHEMY_BINDS']:
if not self.app.debug:
log.debug("Connecting -> DEFAULT. Unable to get Flask-SQLAlchemy bind configuration. Outputting default bind." )
return orm.Session.get_bind(self, mapper, clause)
# Writes go to the master
elif self._flushing: # we who are about to write, salute you
log.debug("Connecting -> MASTER")
return state.db.get_engine(self.app, bind='master')
# Everything else goes to the slave
else:
log.debug("Connecting -> SLAVE")
return state.db.get_engine(self.app, bind='slave')
class AutoRouteSQLAlchemy(SQLAlchemy):
def create_scoped_session(self, options=None):
"""Helper factory method that creates a scoped session."""
if options is None:
options = {}
scopefunc=options.pop('scopefunc', None)
return orm.scoped_session(
partial(AutoRouteSession, self, **options), scopefunc=scopefunc
)
@andyxning
Copy link

This can be done by declaring two models with the same name implementing two different db models.
We can also implement two databases with one table of the same table name.
An example can be like this:

app = Flask(__name__)
app.config['SQLALCHEMY_BINDS'] = {'rw': 'rw', 'r': 'r'}
db = SQLAlchemy(app)
db.Model_RW = db.make_declarative_base()

class A(db.Model):
    __tablename__ = 'common'
    __bind_key__ = 'rw'

class B(db.Model_RW):
    __tablename__ = 'common'
    __bind_key__ = 'r'

@NickWoodhams
Copy link

@andyxning

Getting the error TypeError: make_declarative_base() takes at least 2 arguments (1 given) when I use that code.

@NotSoShaby
Copy link

NotSoShaby commented Nov 7, 2019

How can I integrate this with a sessionmaker (currently i pass an engine to the session manager in the begging)?

engine = SessionManager.get_engine(uri)
session = sessionmaker(bind=engine)()

I tried to make the session object like this:

db = AutoRouteSQLAlchemy()
session = db.session

but i got:

AttributeError: 'scoped_session' object has no attribute '_autoflush'

later on in the code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment