"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

PyMySQLをとりあえず使うオリジナルサンプル/Python3+MariaDB

f:id:arakan_no_boku:20211127225752p:plain

目次

PyMySQLをとりあえず使う

PythonMariaDBを扱うドライバです。

以前使っていた「mysql-connector」は「2019年4月」で更新が止まっていて「非推奨、公式バージョン mysql-connector-python を使え」となってましたが、こちらは、「oracle」が開発しているみたいなので、やめることにしました。

仕事ならこちらでしょうが、個人的に使うのは、できたら個人でがっばっているものを試したいからです。

今回はPyMySQLにしました。

github.com

PyMySQLは「Inada Naoki(methane)」さんが開発されてます。

この方は、Djangoの推奨ドライバになっている「mysqlclient」の開発者でもありますし、Exsampleもあって、とっつきやすそうなので、いいかなと思ってます。

 

PyMySQLのインストール

あくまでドライバレベルなので、DBに接続して、SQL文が実行できて、結果を取り出せる・・以上!・・みたいな感じです。

インストールはPIPで簡単にできます。

pip install PyMySQL

 

PyMySQLのオリジナルサンプル

テーブルはExsampleに書いてある、こいつを使います。

CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `email` varchar(255) COLLATE utf8_bin NOT NULL,
    `password` varchar(255) COLLATE utf8_bin NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
AUTO_INCREMENT=1 ;

ただ、Exsampleまでコピペでは、あまりに芸がありません。

 

オリジナルのExsampleを作ってみました。

import pymysql
import pymysql.cursors
# import traceback


def get_connection(hostname, username, userpass, dbname):
    # コネクションを取得する。カーソルは辞書型を利用する
    return pymysql.connect(host=hostname,
                           user=username,
                           password=userpass,
                           database=dbname,
                           charset='utf8mb4',
                           cursorclass=pymysql.cursors.DictCursor)


# コネクションを取得する
connection = get_connection('localhost', 'user', 'pass', 'db')

# 更新するSQL文をおさめるリスト
sqllist = []
# SQL文に対応するパラメータを収めたタプルを格納するリスト
paramlist = []

# Delete文のサンプル
sqllist.append("delete from users")
paramlist.append(())

# insert文のサンプル
sqllist.append(
    "INSERT INTO users (email, password) VALUES (%s, %s)")
paramlist.append(('webmaster@python.org', 'very-secret'))

# update文のサンプル
sqllist.append(
    "update users set password=%s where email=%s")
paramlist.append(('updated-pass', 'webmaster@python.org'))

# 追加のインサート文 あとでSQLエラーを発生させるためのサンプル
sqllist.append(
    "INSERT INTO users (email, password) VALUES (%s, %s)")
paramlist.append(('arakan-bokur@python.org', 'betsuni-iikedo'))

# トランザクションの開始
connection.begin()
with connection.cursor() as cursor:
    for index, sql in enumerate(sqllist):
        try:
            cursor.execute(sql, paramlist[index])
        except Exception as e:
            print("SQL ERRORがあったのでロールバックしました。")
            print(e)
            # print(traceback.format_exc())
            # ロールバック/これを書いておかないと直前までの更新がコミットされる
            connection.rollback()

# 変更をコミットする
connection.commit()

# SELECT文のサンプル
with connection.cursor() as cursor:
    sql = "SELECT id, password FROM users WHERE email=%s"
    cursor.execute(sql, ('webmaster@python.org',))
    # 結果はカラム名がキーになった辞書をレコード数分収めたリストで返る
    result = cursor.fetchall()
    for r_dic in result:
        # カラム名をキーにして辞書から値を取得する
        print(r_dic["id"], "/", r_dic["password"])

 

オリジナルサンプルのポイントを補足

まず、コネクションのところです。

こんな記述があります。

cursorclass=pymysql.cursors.DictCursor

これは、SELECT結果(fetch)を辞書型で返すクラスを指定しています。

これを指定しないと、結果の値だけがタプルでかえってきて、扱いづらいので、この指定はほぼ固定でしょうね。

 

あと、更新(insert,update,delete)です。

PyMySQLはデフォルトでオートコミットがOFFになっています。

なので、単発でinsert文とか発行しても、必ず、コミットしないといけません。

面倒そうに見えますが、実際に更新文をトランザクションでくくらないで実行することはありえないですから、問題はないと思います。

Exsampleでは、begin()・commit()でくくってトランザクションにしてます。

SQLエラーは例外を発生させてくれるので、tryでひろってrollback()をしてます。

 

SQLプレースホルダは「?」ではなく、「%s」のようなsprintfとかで使う形式で指定し、executeの第二引数で、そこにあてはめる値をタプルで渡すようになってます。

なので、上記サンプルみたいに、SQL文の文字列とバインドするタプルをセットでリストにセットしておけば、ループで回して処理できていい感じです。

 

ん・・。

こんなものでしょうか・

 

テーブル・カラム名をバッククォート(`)で囲っていない理由

PyMySQLとは直接関係ない部分での補足を1点だけ。

オリジナルのExsampleをみると、SQL文が以下のように書かれてます。

INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)

ちょっとわかりにくいですが、テーブル名・カラム名が「バッククォート(`)」で囲まれています。

これはMySQL側で予約語とされている名前・・例えば「table」とか「lock」とか・・をテーブル名やカラム名に使ってもエラーにならないようにする書き方です。

でも、自分は逆にそんな名前をうっかりテーブル名・カラム名に使ったら、エラーになってほしいので、それはつけてません。

このへんは、お好みではないかと思います。

 

サンプル実行例:例外を発生させてみる

サンプルを実行すると。

1 / updated-pass

みたいに結果を表示します。

この「1」の部分は、オートインクリメントで実行する都度変わります。

では。

SQLエラーを発生させて、ちゃんとロールバックしてるかを確認します。

以下のSQLの「users」を「user」に変えて、テーブルが存在しないというSQLエラーをわざと発生させてみます。

INSERT INTO user (email, password) VALUES (%s, %s)

一度、データを消して実行すると。

SQL ERRORがあったのでロールバックしました。
(1146, "Table 'db.user' doesn't exist")

 

ちゃんとエラーがでて、取得データは空・・つまり、ちゃんとロールバックしてます。

よしよし。

いけそうです。