flask-sqlalchemy multiple databases
사용하기
flask-sqlalchemy 에서 기본적으로 SQLALCHEMY_DATABASE_URI
설정을 통해서 mapping class 에 정의된 테이블들이 DB와 연결되게 된다. 그런데 1개 이상의 DB와 연결해야하는 경우가 생긴다. 기존의 회원정보를 같이 쓰는 다른 서비스의 경우가 대표적인 케이스이다. 이럴경우, flask-sqlalchemy 에서는 SQLALCHEMY_BINDS
를 통해서 여러 데이터베이스 URI를 지정할 수 있게 해주고 있다.
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:3306/test1'
app.config['SQLALCHEMY_BINDS'] = {
'test2': 'mysql+pymysql://root:root@localhost:3306/test2',
'test1': 'mysql+pymysql://root:root@localhost:3306/test1'
}
위의 설정을 보면, SQLALCHEMY_DATABASE_URI
를 지정하면서 SQLALCHEMY_BINDS
를 지정하고 있다. SQLALCHEMY_BINDS
는 키-값 형태로 지정하면 되는데, test1, test2 와 같이 지정을 해두었다. SQLALCHEMY_DATABASE_URI
에 지정한 값은 디폴트 값으로 지정되게 된다.
class User(db.Model):
__tablename__ = "tb_user"
__bind_key__ = 'test2'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Store(db.Model):
__tablename__ = "tb_store"
__bind_key__ = 'test1'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Integer, primary_key=True)
mapping class 에는 위와 같이 __bind_key__
항목에 SQLALCHEMY_BINDS
에 지정한 키(key)를 넣어주면 된다. 예를들면 위의 코드에서는 User(tb_user
테이블)은 test2 의 DB와 연결되고 Store(tb_store
테이블)는 test1 의 DB 와 연결되는 것이다. __bind_key__
항목을 설정하지 않으면 SQLALCHEMY_DATABASE_URI
에 설정한 URI 로 연결이 된다.
테이블을 생성하거나 삭제 할때에도 bind 를 아래와 같이 지정할 수가 있다.
# Refer http://flask-sqlalchemy.pocoo.org/2.1/binds/
>>> db.create_all()
>>> db.create_all(bind=['test1'])
>>> db.create_all(bind='test2')
>>> db.drop_all(bind=None)
__bind_key__
는 어떻게 저장되는가?
__bind_key__
는__tablename__
과는 다르게 flask-sqlalchemy 에서만 있는 부분이다.__bind_key__
를 지정하게 되면 Table 클래스의info
dict 에bind_key
항목으로 들어가게 된다.- 아래의 코드를 보면 테이블을 정의하는 클래스를 만들때
db.Model
을 상속 받게 되는데, db 는 flask-sqlalchemy 의 SQLAlchemy 클래스의 인스턴스이고 Model 은 그 클래스의 변수이다.
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class User(db.Model):
...
db.Model => SQLAlchemy 의 self.Model = self.make_declarative_base(model_class, metadata)
- Model 은
make_declarative_base()
함수에 의해서 생성 되는데 내부에서 sqlalchemy 의declarative_base()
함수를 호출하면서 _BoundDeclarativeMeta 클래스를 metaclass 로 지정하고 있다.
# flask_sqlalchemy/__init__.py
def make_declarative_base(self, model, metadata=None):
"""Creates the declarative base."""
base = declarative_base(cls=model, name='Model',
metadata=metadata,
metaclass=_BoundDeclarativeMeta)
if not getattr(base, 'query_class', None):
base.query_class = self.Query
base.query = _QueryProperty(self)
return base
- _BoundDeclarativeMeta 클래스는 생성자에서 파라미터로 전달된
d
에__bind_key__
가 있는지 확인하고 있으면 table.info 의bind_key
항목으로 넣고 있다.__init__()
함수로 전달되는 항목들을 찍어보면 아래와 같다.
# flask_sqlalchemy/__init__.py
class _BoundDeclarativeMeta(DeclarativeMeta):
def __new__(cls, name, bases, d):
# if tablename is set explicitly, move it to the cache attribute so
# that future subclasses still have auto behavior
if '__tablename__' in d:
d['_cached_tablename'] = d.pop('__tablename__')
return DeclarativeMeta.__new__(cls, name, bases, d)
def __init__(self, name, bases, d):
# 디버깅을 위해 추가한 부분.
print('name :{}'.format(name))
print('bases :{}'.format(bases))
print('d :{}'.format(d))
bind_key = d.pop('__bind_key__', None) or getattr(self, '__bind_key__', None)
DeclarativeMeta.__init__(self, name, bases, d)
if bind_key is not None and hasattr(self, '__table__'):
self.__table__.info['bind_key'] = bind_key
name :User
bases :(<class 'flask_sqlalchemy.Model'>,)
d :{'__module__': '__main__', '__qualname__': 'User', '__bind_key__': 'test2', 'id': Column(None, Integer(), table=None, primary_key=True, nullable=False), 'name': Column(None, String(), table=None), '_cached_tablename': 'tb_user'}
name :Store
bases :(<class 'flask_sqlalchemy.Model'>,)
d :{'__module__': '__main__', '__qualname__': 'Store', '__bind_key__': 'test1', 'id': Column(None, Integer(), table=None, primary_key=True, nullable=False), 'name': Column(None, Integer(), table=None, primary_key=True, nullable=False), '_cached_tablename': 'tb_store'}
Reference