PythonでシンプルなORMライブラリ、datasetを使ってみた

2019.06.03

サーモン大好き横山です。
今日はPythonでORM使いたいけど、雑に辞書型にマッピングしてくれるだけで良いというときに便利な dataset について紹介します。

install方法

venv環境にinstallして使って行きます。

python3 -m venv venv
. venv/bin/activate

pip で datasetをinstallします。

pip install dataset

今回はMySQLにつなぐために、 mysqlclient もinstallします。

pip install mysqlclient

mysqldへデータ投入

Other MySQL Documentationworld database を利用して確認します。

curl -O https://downloads.mysql.com/docs/world.sql.zip
unzip world.sql.zip

今回はlocalにmysqldをたてて、そこにデータを入れます。

mysql -uroot -p < world.sql

使用してみる

最初に local に接続するコードを書きます。(ここではload.pyに保存) dataset.connect の引数は SQLAlchemyのEngine URLで接続します。 dialect[+driver]://user:password@host/dbname[?key=value..] 詳しくは、SQL Alchemyのcreate_engine のドキュメントを参照してください。

#!/usr/bin/env python

import dataset

db = dataset.connect('mysql://root:password@localhost/world')

こちらのコードを実行後、対話モードを起動して操作していきます。

python -i load.py

cityのcountを取得

db['city'] で cityのテーブルの参照を取得します。 その後 count 関数を実行すると、cityテーブルの行数を取得できます。

>>> city = db['city']
>>> city.count()
4079

cityのテーブルからいろいろ取得してみた

find 関数を使い、cityデータを取得します。 _limit の引数で10件と指定して取得します。指定しない場合は、全件(4079件)取得できます。

>>> for c in city.find(_limit=10):
...   print(c)
...
OrderedDict([('ID', 1), ('Name', 'Kabul'), ('CountryCode', 'AFG'), ('District', 'Kabol'), ('Population', 1780000)])
OrderedDict([('ID', 2), ('Name', 'Qandahar'), ('CountryCode', 'AFG'), ('District', 'Qandahar'), ('Population', 237500)])
OrderedDict([('ID', 3), ('Name', 'Herat'), ('CountryCode', 'AFG'), ('District', 'Herat'), ('Population', 186800)])
OrderedDict([('ID', 4), ('Name', 'Mazar-e-Sharif'), ('CountryCode', 'AFG'), ('District', 'Balkh'), ('Population', 127800)])
OrderedDict([('ID', 5), ('Name', 'Amsterdam'), ('CountryCode', 'NLD'), ('District', 'Noord-Holland'), ('Population', 731200)])
OrderedDict([('ID', 6), ('Name', 'Rotterdam'), ('CountryCode', 'NLD'), ('District', 'Zuid-Holland'), ('Population', 593321)])
OrderedDict([('ID', 7), ('Name', 'Haag'), ('CountryCode', 'NLD'), ('District', 'Zuid-Holland'), ('Population', 440900)])
OrderedDict([('ID', 8), ('Name', 'Utrecht'), ('CountryCode', 'NLD'), ('District', 'Utrecht'), ('Population', 234323)])
OrderedDict([('ID', 9), ('Name', 'Eindhoven'), ('CountryCode', 'NLD'), ('District', 'Noord-Brabant'), ('Population', 201843)])
OrderedDict([('ID', 10), ('Name', 'Tilburg'), ('CountryCode', 'NLD'), ('District', 'Noord-Brabant'), ('Population', 193238)])

特定の値を取得するときは、 find_one を使います。取得した値は、OrderedDict で取得できるので、カラム名をキーに値を取得できます。

>>> tokyo = city.find_one(Name='Tokyo')
>>> print(tokyo)
OrderedDict([('ID', 1532), ('Name', 'Tokyo'), ('CountryCode', 'JPN'), ('District', 'Tokyo-to'), ('Population', 7980230)])

複数候補がある場合は、1件目を取得します。

>>> for ja in city.find(CountryCode='JPN', _limit=5):
...   print(ja)
...
OrderedDict([('ID', 1532), ('Name', 'Tokyo'), ('CountryCode', 'JPN'), ('District', 'Tokyo-to'), ('Population', 7980230)])
OrderedDict([('ID', 1533), ('Name', 'Jokohama [Yokohama]'), ('CountryCode', 'JPN'), ('District', 'Kanagawa'), ('Population', 3339594)])
OrderedDict([('ID', 1534), ('Name', 'Osaka'), ('CountryCode', 'JPN'), ('District', 'Osaka'), ('Population', 2595674)])
OrderedDict([('ID', 1535), ('Name', 'Nagoya'), ('CountryCode', 'JPN'), ('District', 'Aichi'), ('Population', 2154376)])
OrderedDict([('ID', 1536), ('Name', 'Sapporo'), ('CountryCode', 'JPN'), ('District', 'Hokkaido'), ('Population', 1790886)])
>>> jpn = city.find_one(CountryCode='JPN')
>>> print(jpn)
OrderedDict([('ID', 1532), ('Name', 'Tokyo'), ('CountryCode', 'JPN'), ('District', 'Tokyo-to'), ('Population', 7980230)])

各カラムにアクセスするには、辞書のキーにカラム名を指定すれば、値が取れます。

>>> print(tokyo['ID'])
1532
>>> print(tokyo['Name'])
Tokyo
>>> print(tokyo['Population'])
7980230

カラム名一覧は、テーブル参照の columns から取れます。これを利用して、レコードの値を一気に出力したりもできます。

>>> for col_name in city.columns:
...   print(f'{col_name:>12}:{tokyo[col_name]}')
...
          ID:1532
        Name:Tokyo
 CountryCode:JPN
    District:Tokyo-to
  Population:7980230

トランザクション・更新・挿入・削除

databaseの参照に begin commit rollback の関数があります。 まず、トランザクションをはり、Tokyoの人口を更新したあとにrollbackします。 rollbackするまでは更新した値になっていますが、rollback後は元に戻ります。

>>> tokyo = city.find_one(Name='Tokyo')
>>> db.begin() # トランザクション開始
>>> tokyo['Population'] = 114514 # 人口更新
>>> city.update(tokyo, ['ID']) # IDが一致するレコードを更新
1
>>> print(city.find_one(Name='Tokyo')['Population']) # トランザクション内では更新されている
114514
>>> db.rollback() # トランザクション破棄
>>> print(city.find_one(Name='Tokyo')['Population']) # トランザクション前の値にもどる
7980230

次に、もう一度トランザクションをはり、夕張市のレコードをinsertし、commitします。

>>> # IDはauto_incrimentで値がはいるので省略
>>> yubari = dict(Name='Yubari',CountryCode='JPN',District='Hokkaido',Population=8033)
>>> db.begin() # トランザクション開始
>>> city.insert(yubari) # cityのテーブルに夕張を追加
4080
>>> print(city.find_one(Name='Yubari')) # トランザクション内では挿入されている
OrderedDict([('ID', 4080), ('Name', 'Yubari'), ('CountryCode', 'JPN'), ('District', 'Hokkaido'), ('Population', 8033)])
>>> db.commit() # トランザクション終了
>>> print(city.find_one(Name='Yubari')) # トランザクション抜けても値が残っている
OrderedDict([('ID', 4080), ('Name', 'Yubari'), ('CountryCode', 'JPN'), ('District', 'Hokkaido'), ('Population', 8033)])

まとめ

datasetを使えば特にsqlを書かずにレコードを取得・追加・更新ができ、OrderedDictにレコードをマッピングしてくれるので、とても楽です。 「SQLは書きたくない!けど、ORMで楽したい!」という方にはとてもとっつきやすいライブラリだと思います。