PythonでTUIアプリを作ろう 第二部入魂編 ( その7 Pythonのデータアクセス機能 )

ListDBPのデータベースにMySQLを使用してみる。


今回は、まず「Pythonとデータベース」について説明したうえで、「ListDBPにおける複数データベース対応」について記述します。


[1] PythonによるDBアクセス

データを格納するには、何らかのデータベースが必要ですが、RDB(Relational Database)を使用するのが一般的でしょう。
Pythonでは、DB-APIと呼ばれるものが標準ライブラリとして含まれており、SQLite3というシングルユーザー用データベースが使用可能です。

上記のサイトによると、

SQLite は、軽量なディスク上のデータベースを提供する C ライブラリです。別のサーバプロセスを用意する必要なく、 SQL クエリー言語の非標準的な一種を使用してデータベースにアクセスできます。一部のアプリケーションは内部データ保存に SQLite を使えます。また、SQLite を使ってアプリケーションのプロトタイプを作り、その後そのコードを PostgreSQL や Oracle のような大規模データベースに移植するということも可能です。

とのこと。
なにゆえ、移植が可能かといえば、Pythonによるデータベースアクセスには、事前に決められたインターフェース"PEP 249"があり、ほとんどのモジュールは、この標準にしたがって設計されているからです。

PEPに従ったDB-APIでは、importするモジュール以外は、同じような構造になります。SQLite3以外のデータベースも、上記PEPに従ったインタフェースモジュールが提供されていますので、スクリプトは下記のような構造になるでしょう。

# Importするモジュールは、それぞれのDB用  
  
# これは標準DBのSQLite3用  
import sqlite3  
  
# MySQLの場合  
import pymysql  
  
# Postgresの場合  
import psycopg2  
  

ここからは、同じようなコードとなるはずです。Sqlite3であれば、こんな感じです。

con = sqlite3.connect('example.db')  
cur = con.cursor()  
cur.execute('''CREATE TABLE stocks  
               (date text, trans text, symbol text, qty real, price real)''')  
cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)")  
  
con.commit()  
con.close()  

ここでは、DB-APIや各データベースモジュールの詳細は説明はしません。様々なサイトがありますから、そちらを参照してください。


DB-APIの限界

個人用のデータ管理ならSQLite3が最適ですが、あくまでシングルユーザー用ですから、Webアプリのように、マルチユーザーからアクセスする場合には、MySQLやPostgressなどを使うのが一般的です。
理想を言えば、「1つのスクリプトで複数データベースをサポートしたい」わけですが、現実的には無理があります。DB-APIの基本的な流れは、先に述べたように標準化されていますが、あくまで関数が共通化されただけで、SQLレベルまで合わされているわけではないからです。
例えば、Prepareステートメントで使われるバインド変数は、SQLite3では’?’で表しますが、他のDBでは’%s’だったりします。また、標準SQL以外の関数は、それぞれのRDBの仕様に依存してしまいます。
このようにRDBによる違いが少なくないので、「共通性の追求には限界がある」わけなのです。


[2] ListDBPをMySQL上で動かす。

そこで、今回のアプリケーションListDBPでは、「データベースによって異なる部分を抜き出し、個別対応する」という泥臭い施策になっています。
以下、簡単に説明しておきます。

(1) データベースの選択

実行時にパラメータとして、データベース名と接続文字列を取得します。

if __name__=="__main__":  
    if len(sys.argv) == 3:  
        database_name = sys.argv[1]  
        database_connect = sys.argv[2]  
    else:  
        database_name = 'SQLITE3'  
        database_connect = './db/ListDB.sqlite3'  
        #database_name = 'MYSQL'  
        #database_connect = 'mysql://user01:pass01@localhost/ListDB'  
    main(database_name, database_connect)  

(2) データベースモジュールでの処理

ListDBInterfaceクラスで、対応するデータベースモジュールをImport、それぞれの処理を委譲していくという仕組みで、複数データベースに対応します。
SQLite3対応はdbsqlite3.pyに、MySQL用はdbmysql.pyに記述してあります。今回はスクリプトに組み入れていませんが、Postgress用のものをdbpostgres.pyとして残しておきました。

class ListDBInterface:  
    def __init__(self, database_name, database_connect):  
        if (database_name == "SQLITE3"):  
            from . import dbsqlite3  
            self.db = dbsqlite3.ListDBSQLite3(database_connect)  
        elif (database_name == "MYSQL"):  
            from . import dbmysql  
            self.db = dbmysql.ListDBMySQL(database_connect)  
  
        self.connection = self.db.connection  
        self.cursor = self.db.cursor  
  
    # --------------------------------------------------------  
    def get_drop_meta_sql(self):  
        return self.db.get_drop_meta_sql()  
    def get_define_sql(self):  
        return self.db.get_define_sql()  
    def get_insert_meta_sql(self):  
        return self.db.get_insert_meta_sql()  
    def get_update_meta_sql(self, dbName):  
                     :  
                     :  

[3] MySQL環境の整備

アプリケーションを実行するには、まずMySQL環境をセットアップする必要があります。以下、Ubuntu環境での実行について記述します。

(1) MySQLのインストール

何はともあれ、MySQL本体をインストールしないと始まりません。

$ sudo apt-get install mysql-server  

(2) セットアップ

MySQL環境をセットアップしていきます。

1. rootのパスワードを設定
$ sudo mysql -u root  
mysql> alter user 'root'@'localhost'  identified by 'xxxx' ;  
mysql> flush privileges;  
2. ListDBデータベースの作成
mysql> create database ListDB;  
3. ユーザーの作成

作成したListDBデータベースについて、全権限を持つユーザーを作成します。ここでは、ユーザー「user01」をパスワード「pass01」で設定しました。

mysql> create user 'user01'@'%' identified by 'pass01';  
mysql> create user 'user01'@'localhost' identified by 'pass01';  
mysql> grant all privileges on ListDB.* to user01@'%';  
mysql> grant all privileges on ListDB.* to 'user01'@'localhost';  
4. 外部接続の許可

外部からの接続を許可するには、/etc/mysql/mysql.conf.d/mysqld.cnfに設定が必要なようです。

bind-address = 127.0.0.1  
     ↓  
bind-address = (接続したいマシンのIPアドレス)  
#どのIPからも接続許可する場合は、bind-address行をコメントアウト。  
5. Python MySQLモジュールのインストール

いくつかのMySQL対応モジュールがあるようですが、ここではPyMySQLを使用していますので、pipでインストールします。

$ pip3 install PyMySQL  

[5] スクリプトの変更と実行

環境が整備されたら、まずはMySQLにデータをロードします。その後、スクリプトを実行、内容を確認していきます。

(1) データロード

load_db.shスクリプトを下記のように書き換えて実行します。

python3 load_db.py 'MYSQL' 'mysql://user01:pass01@localhost/ListDB' './csv'  
#python3 load_db.py 'SQLITE3' './db/ListDB.sqlite3' './csv'  

(2) スクリプトの実行

list.pyの起動部を下記のように書き換えて実行します。

if __name__=="__main__":  
    if len(sys.argv) == 3:  
        database_name = sys.argv[1]  
        database_connect = sys.argv[2]  
    else:  
        #database_name = 'SQLITE3'  
        #atabase_connect = './db/ListDB.sqlite3'  
        database_name = 'MYSQL'  
        database_connect = 'mysql://user01:pass01@localhost/ListDB'  
    main(database_name, database_connect)  

以上となります。
なお、今回説明したMySQL対応は、あくまで「Pythonおける複数データベース対応」のテスト用に作成したものですから、一つの参考としてご理解ください。


ソースコードについて

GitHubに登録しました。今回のコードは、ListDBPとなります。