Flask-sqlalchemyでマルチデータベースでの生ＳＱＬの使用

#SQL後処理
* tableを動的に戻そうとしてハマった。
　下記、参考になりました。
　https://python5.com/q/dqtofqqq

```python
　　　sqlalchemy での　API化に必須なコード　　
     items = [{column: value for column, value in rowproxy.items()} for rowproxy in items]     ##   https://python5.com/q/dqtofqqq

```


#update

結局、追加したコードは下記です。
毎回、SQLを発行するたびに，空打ちのように`sql_attach_DB()`を呼んでいます。
（これどうやら、毎回ATTACHしないと消えるみたいですね。）

#引っ張り方のまとめ
get_binds()でバインド情報を取得

* SQLALCHEMY_BINDSの値　→　k.info["bind_key"]    →　　db_classify
* データファイル　→　v.url.database　 →　'sqlite:///classify/data.db'
* クエリの呼び出し方　→　バインドキー.テーブル名　→　db_classify.classify_master

本来はテーブル名から逆参照して、SQL自体を書き換える方が汎用的だろうけど
バインドキーを覚えるぐらいは、そんなに苦にならないかと思い、そのままベタ書きすることにした。

以上

```python
DB_CLASSIFY = 'sqlite:///classify/data.db'
app.config['SQLALCHEMY_BINDS'] = {"db_classify" : DB_CLASSIFY}  
sql = r"SELECT * FROM db_classify.classify_master" 

##SQLITE TABLE ATTACHING
def sql_attach_DB():
    binds = db.get_binds(app)       
    for k,v in binds.items():
        if k.info:
            if k.info['bind_key']:
                sql= "ATTACH DATABASE "+ "'" + v.url.database + "'" + " as "+ k.info["bind_key"]
                db.session.execute(sql)
    
def model_query_exec():
    sql_attach_DB()
    results = db.session.execute(text(sql))
    return results
```

#テーマ

Flask-sqlalchemyのSQLクエリで、マルチデータベースを呼び出すとエラーになる。
SQLクエリは、`ClassifyMaster.query.all()`を実施し、`SQLALCHEMY_TRACK_MODIFICATIONS = False`でログで出したものをコピペ


```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
db = SQLAlchemy(app)

class Master(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String)                               
    sql = db.Column(db.String(500))

class ClassifyMaster(db.Model):
    __tablename__ = "classify_master"
    __bind_key__ = "db_classify"
    #db.Column情報の設定
    id = db.Column(db.Integer, primary_key=True)

DB_MAIN = 'sqlite:///data.db'
DB_CLASSIFY = 'sqlite:///classify/data.db'
SQLALCHEMY_DATABASE_URI = DB_MAIN
SQLALCHEMY_BINDS = {   "db_classify" : DB_CLASSIFY}    

db.session.add(Master(title = '品種分類表', sql = """SELECT ClassifyMaster.id FROM ClassifyMaster""" ))
```

#エラー内容
バインド先のデータベースが見つからない

```
(Pdb) db.session.execute(text)
INFO:sqlalchemy.engine.base.Engine:SELECT ClassifyMaster.id FROM ClassifyMaster
INFO:sqlalchemy.engine.base.Engine:()
*** sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: ClassifyMaster
[SQL: SELECT ClassifyMaster.id FROM ClassifyMaster]
(Background on this error at: http://sqlalche.me/e/e3q8)
```

#どうやって調べていくのか？

丸一日かけてマルチデータベースについて調べるが、有力回答が得られず。

結局はSQLITEの使い方を調べまくり、ハンドで`ATTACH DATABASE`をつけたら、クエリが動くことを確認した。
https://sqlite-r.com/attach_database

```python

(Pdb) db.session.execute(r"""ATTACH DATABASE 'classify\data.db' as x;""")
(Pdb) db.session.execute('SELECT * FROM x.classify_master')
INFO:sqlalchemy.engine.base.Engine:SELECT * FROM x.classify_master
INFO:sqlalchemy.engine.base.Engine:()
<sqlalchemy.engine.result.ResultProxy object at 0x000001F679DDA730>
```
一応解決策は見出せたが、もっとスマートにできないかと調査しはじめる。

#Flask-Sqlalchemyの分かったこと

ソース解析　→　さすがスクリプト言語。
分かりやすい　→　万歳！

```python
     def get_tables_for_bind(self, bind=None):
        """Returns a list of all tables relevant for a bind."""
        result = []
        for table in itervalues(self.Model.metadata.tables):
            if table.info.get('bind_key') == bind:
                result.append(table)
        return result

    def get_binds(self, app=None):
        """Returns a dictionary with a table->engine mapping.

        This is suitable for use of sessionmaker(binds=db.get_binds(app)).
        """
        app = self.get_app(app)
        binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
        retval = {}
        for bind in binds:
            engine = self.get_engine(app, bind)
            tables = self.get_tables_for_bind(bind)
            retval.update(dict((table, engine) for table in tables))
        return retval

    def _execute_for_all_tables(self, app, bind, operation, skip_tables=False):
        app = self.get_app(app)

        if bind == '__all__':
            binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
        elif isinstance(bind, string_types) or bind is None:
            binds = [bind]
        else:
            binds = bind

        for bind in binds:
            extra = {}
            if not skip_tables:
                tables = self.get_tables_for_bind(bind)
                extra['tables'] = tables
            op = getattr(self.Model.metadata, operation)
            op(bind=self.get_engine(app, bind), **extra)

```

##テーブルの取り出し

そもそもbindって、Flask側で宣言してたやん
（sqlarchemy関係あらへん・・・）

これが第一の勘違い




```python
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
(Pdb) binds
[None, 'db_classify']
```
##エンジンの取得

* 取得したbindsを引数に渡す

 `get_engine(app=None, bind=None) #Returns a specific engine.`


```python
(Pdb) db.get_engine(app, binds[0])
Engine(sqlite:///C: .... data.db)
(Pdb) db.get_engine(app, binds[1])
Engine(sqlite:///C: .... classify\data.db)
```

おー、取れた取れた！

ついでにテーブルも

```python
(Pdb) db.get_tables_for_bind(bind=binds[1])
[Table('classify_master', MetaData(bind=None), 
Column('id', Integer(), table=<classify_master>, 
primary_key=True, nullable=False))]
```

ここの `<classify_master>`になっている奴が、たぶんSqlAlchemyでバインドされるんやろ

`ATTACH DATABASE`で、適当なネームスペースにデータソースをくっ付けたら、ええんやな！
ということで、方針決まり

さあ、コーディング開始！

#まとめ

結局は、Flask-SqlalchemyはTEXT()をカバーしていなかったのか？
→　この件、ISSUEを挙げるべきなのか？

いや、そもそも生クエリ嫌だからORMにしたんじゃねぇ？
って返されるかもしれない

しかしながら、クエリを動的にデータベースに突っ込みたいというニーズもあり、
やはり生クエリでの処理は必須だと思われる。

つづく