Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Identical databases in Flask-SQLAlchemy

I've already asked a similar question, but I thought maybe I could rephrase it, or show what I've done further to shed some light onto what's going on here.

Currently I have 2 identical databases, and I've attempted to solve the problem (as per another question I saw) like this:

class BaseTable(db.Model):
    __tablename__ = 'TableName'
    col = db.Column(db.Integer)

class SubTable1(BaseTable):
    __bind_key__ = 'bind1'

class SubTable2(BaseTable):
    __bind_key__ = 'bind2'

The problem with this is that now the most recent bind is used everywhere, so if I do this somewhere else:

SubTable1.query.filter_by(col=12).all()

Then it gets results from the second database. If I were to switch the locations of the SubTable classes, then the results are the same (Edit for clarity: by which I mean that the results come from whatever bind is defined last, if they were to be switched, it would instead query from 'bind2' instead of 'bind1' as it currently does). I don't really know what to do, so if you can help in any way that would be awesome.

Thanks.

EDIT: If it's impossible (or you simply know a better or even different way) to do this, please let me know. If I could do something like having two different db objects, that would be good as well, I just don't really know how to do that or what kind of implications that would have.

EDIT 2: After toiling with this for hours and hours, I've finally come to a conclusion on how to do this.

In __init__.py:

db1 = SQLAlchemy(app)
db2 = SQLAlchemy(app)

In models.py:

class Table1(db1.Model):
    __tablename__ = 'TableName'
    __bind_key__ = 'bind1'
    col = db1.Column(db1.Integer)

class Table2(db2.Model):
    __tablename__ = 'TableName'
    __bind_key__ = 'bind2'
    col = db2.Column(db2.Integer)

The reason for this nonsense is that binds can only be defined once and not changed, and no two table names can be the same, even if the binds are different. So you have to make 2 MetaData instances or else SQLAlchemy gets mad. So it turns out the problem is a limitation in SQLAlchemy.

like image 371
miscsubbin Avatar asked Jun 14 '13 13:06

miscsubbin


People also ask

How do I join two databases in Flask?

SQLAlchemy can connect to more than one database at a time. It refers to different engines as “binds”. Flask-SQLAlchemy simplifies how binds work by associating each engine with a short string, a “bind key”, and then associating each model and table with a bind key.

Is Flask-SQLAlchemy the same as SQLAlchemy?

What is Flask-SQLAlchemy? Flask-SQLAlchemy is an extension for Flask that aims to simplify using SQLAlchemy with Flask by providing defaults and helpers to accomplish common tasks. One of the most sought after helpers being the handling of a database connection across the app.

Which db is used in SQLAlchemy?

Supported Databases. SQLAlchemy includes dialects for SQLite, Postgresql, MySQL, Oracle, MS-SQL, Firebird, Sybase and others, most of which support multiple DBAPIs.

Is SQLAlchemy good for ETL?

One of the key aspects of any data science workflow is the sourcing, cleaning, and storing of raw data in a form that can be used upstream. This process is commonly referred to as “Extract-Transform-Load,” or ETL for short.


1 Answers

I don't know what __bind_key__ is, but there are many approaches to using a single Session with multiple binds. Session itself can be bound directly: to do this, SubTable1 and SubTable2 need to be mapped individually and not part of an inheritance hierarchy, as the Session locates the bind based on the base-most mapped class. In order to share the same MetaData, just map both classes to the same Table object:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class BaseTable(Base):
    __tablename__ = 'some_table'
    id = Column(Integer, primary_key=True)

class SubTable1(Base):
    __table__ = BaseTable.__table__

class SubTable2(Base):
    __table__ = BaseTable.__table__

db1 = create_engine("sqlite:///db1.db", echo=True, logging_name='db1')
db2 = create_engine("sqlite:///db2.db", echo=True, logging_name='db2')

Base.metadata.create_all(db1)
Base.metadata.create_all(db2)

s = Session(binds={SubTable1: db1, SubTable2: db2})

s.add_all([
    SubTable1(),
    SubTable2(),
    SubTable1(),
    SubTable2(),
    SubTable1(),
])

s.commit()

print s.query(SubTable1).all()
print s.query(SubTable2).all()

that's one way. Another, let's actually just use two different MetaData objects, easy enough with mixins:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class BaseTable(object):
    __tablename__ = 'some_table'
    id = Column(Integer, primary_key=True)

class DB1(Base):
    metadata = MetaData()
    __abstract__ = True

class DB2(Base):
    metadata = MetaData()
    __abstract__ = True

class SubTable1(BaseTable, DB1):
    pass

class SubTable2(BaseTable, DB2):
    pass

db1 = create_engine("sqlite:///db1.db", echo=True, logging_name='db1')
db2 = create_engine("sqlite:///db2.db", echo=True, logging_name='db2')

DB1.metadata.create_all(db1)
DB2.metadata.create_all(db2)

s = Session(binds={SubTable1: db1, SubTable2: db2})

s.add_all([
    SubTable1(),
    SubTable2(),
    SubTable1(),
    SubTable2(),
    SubTable1(),
])

s.commit()

print s.query(SubTable1).all()
print s.query(SubTable2).all()

and yes, since we have the two MetaData objects there, we can "bind" them directly, if we want to go that route:

# ... mapping as before

DB1.metadata.bind = db1
DB2.metadata.bind = db2
DB1.metadata.create_all()
DB2.metadata.create_all()

s = Session()  # don't need binds in this case

# ... usage as before
s = Session()
like image 196
zzzeek Avatar answered Sep 21 '22 10:09

zzzeek