Pythonで色々なデータベースをKey-Value Storeとして利用する

軽いアプリをサクッと作りたい時、データモデル含めて走りながら決めていく時、「このテキストを、数値を、dictを、そのまま保存・読込したいのに！」と思うことも割と多いのではないかと思います。

そこで、リレーショナルデータベースをバックエンドDBとして使いつつ、コードからはKey-Value Storeのように気軽に使えるようにするライブラリ「Pycoki（Python Compatible Key-value-store Interface for databases：パイコキ）」を作りました。

Github https://github.com/uezo/pycoki
よろしければStarいただけると幸いです！

この記事では、Pycokiの特長と使い方をご紹介します。

# 特長

- シンプルで簡単: キー・値を取得・設定することのみに特化することで、手続きとか設定とかを極限まで省きました
- 互換性: アプリからの使用方法はそのままに、SQLite(デフォルト), MySQL, SQLServer, PostgreSQLをバックエンドのデータベースとして使用することができます。拡張クラスを実装すれば、その他あらゆるデータベースにも対応可能です
- 名前空間のサポート: キーの他に名前空間を使用できるので、データの種類ごとにデータベースやテーブルを切り替えて参照・保存する必要がありません
- 相互運用性: オブジェクトの保存・再利用といえばPickleですが、PycokiではJSONとしでデータベースに保存します。そのためPython以外のプログラムから利用することができる上に、JSON型をサポートするデータベースであれば要素の条件を指定した検索も可能です

# インストール

Githubのリポジトリから`pip`コマンド一発でインストールすることができます。

```
$ pip install git+https://github.com/uezo/pycoki
```

# 基本的な使い方

はじめに、パイコキモジュールの`start()`を呼び出します。その後のデータの格納、取得、パイコキの終了方法は以下の通りです。

```python
# Pycokiのインポート
import pycoki

# Pycokiの開始
p = pycoki.start("test.db", init_table=True)   #init_table is required for the first access to create data table

# 各種データの格納。json.dumps可能なデータを値として設定可能
p.set("key1", "value1")
p.set("key2", 123.45)
p.set("key3", {"sub_key1": "sub_val2", "sub_key2": 678, "sub_key3": {"dict_key1":"dict_val1", "dict_key2":["array_val1", "array_val2", "array_val3"]}})

# キーを指定した値の取得
print(p.get("key1"))
# dict/listの値の取得（自動でデシリアライズされます）
print(p.get("key3")["sub_key3"]["dict_key2"][1])
# 全データの取得
print(p.get())
# キーの取得
print(p.keys())

# Pycokiの終了
p.close()
```

実行結果：

```
value1
array_val2
{'key1': 'value1', 'key2': 123.45, 'key3': {'sub_key2': 678, 'sub_key3': {'dict_key1': 'dict_val1', 'dict_key2': ['array_val1', 'array_val2', 'array_val3']}, 'sub_key1': 'sub_val2'}}
['key1', 'key2', 'key3']
```

また、パイコキの開始/終了を自動制御し、単に値の設定・取得する方法は以下の通り。内部的にはデータベースに都度接続・切断するため、パフォーマンスは悪いです。

```python
# Quick setter
pycoki.set("key4", "value4", connection_str="test.db")

# Quick getter
val = pycoki.get("key4", connection_str="test.db")
print(val)
```

```
value4
```

# MySQLの使用

デフォルトはSQLiteですが、バックエンドはMySQLに切り替えることができます。この機能を使用するには`MySQLdb`をあらかじめ導入しておいてください。

```python
# Import Pycoki and MySQL extension
import pycoki
from pycoki.mysql import MySQLKeyValueStore

# Start Pycoki
mysql_conn_str = "host=localhost;user=root;passwd=;db=pycokidb;charset=utf8;"
# p = pycoki.start(mysql_conn_str, kvsclass=MySQLKeyValueStore)
p = pycoki.start(mysql_conn_str, kvsclass=MySQLKeyValueStore, init_table=True, init_params=("pycokidb",))  #init_table/init_paramsは初回のみ

# Set and get data
p.set("key1", "Hello MySQL!")
print(p.get("key1"))

# Finish Pycoki
p.close()
```

```
Hello MySQL!
```


# SQL Server / Azure SQL Databaseの使用

SQL Server / Azure SQL Databaseを使用する方法です。`pyodbc`が必要になります。

```python
# Import Pycoki and SQLDatabase extension
import pycoki
from pycoki.sqldb import SQLDBKeyValueStore

# Start Pycoki
sqldb_conn_str = "DRIVER={ODBC Driver 13 for SQL Server};SERVER=******.database.windows.net;PORT=1433;DATABASE=******;UID=******;PWD=******"
# p = pycoki.start(sqldb_conn_str, kvsclass=SQLDBKeyValueStore)
p = pycoki.start(sqldb_conn_str, kvsclass=SQLDBKeyValueStore, init_table=True)  #init_tableは初回のみ

# Set and get data
p.set("key1", "Hello SQL Server!")
print(p.get("key1"))

# Finish Pycoki
p.close()
```

```
Hello SQL Server!
```


# PostgreSQLの使用

PostgreSQLを使用する方法です。`psycopg2`が必要になります。

```python
# Import Pycoki and SQLDatabase extension
import pycoki
from pycoki.pgsql import PgSQLKeyValueStore

# Start Pycoki
pgsql_conn_str = "postgresql://username:password@hostname:port/pycokidb"

# p = pycoki.start(pgsql_conn_str, kvsclass=PgSQLKeyValueStore)
p = pycoki.start(pgsql_conn_str, kvsclass=PgSQLKeyValueStore, init_table=True)  #init_tableは初回のみ

# Set and get data
p.set("key1", "Hello PostgreSQL!")
print(p.get("key1"))

# Finish Pycoki
p.close()
```

```
Hello PostgreSQL!
```

# おわりに

本来的にはNoSQLのエンジンを使えばよいし、ORMもだいぶ助けになると思うのですが、もっと気軽に使えてそこそこ実用にも耐えうるもの、使い慣れたDBや関連他システムのDBと統合できるもの、といったコンセプトで作りました。それにDBにJSON突っ込んでる人、結構いるみたいでしたので...。

最初はdictのインターフェイスをそのままにとも思ったのですが、キー以外に一階層（名前空間）設けたかったのと、dictと同じように使うにはパフォーマンスが出ないため、誤解なきようgetter/setterを介するようにしています。

ユースケースを踏まえてもっと使いやすくしていきたいと思いますので、コメントかGithubのIssueにどしどしご意見いただけると幸いです。

もし気に入っていただけたら、よろしければStarお願いします！
https://github.com/uezo/pycoki