はじめに

Python で MySQL の操作が可能な ORM(OR Mapper) の peewee についての調査メモ。

公式ドキュメントはここ

目次

  1. はじめに
  2. 環境・条件
  3. 詳細
    1. peewee について
    2. DB 接続
    3. テーブル一覧 取得
    4. テーブル 作成
      1. テーブル名を複数形にする
      2. キャメルケース(パスカルケース)のテーブル名を _(アンダースコア) 区切りにする
      3. テーブル名を自分で設定する
      4. Big Integer で id フィールドを定義
      5. 利用可能フィールド
      6. フィールドオプション
    5. テーブル 削除
    6. データ 追加
    7. データ 更新
  4. その他・メモ
    1. MySQL driver not installed! と出る場合
  5. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.5
BuildVersion: 18F132

$ mysqld --version
mysqld Ver 5.7.26 for osx10.14 on x86_64 (Homebrew)

$ python --version
Python 3.6.2 :: Anaconda, Inc.

$ pyenv --version
pyenv 1.2.13

$ pip --version
pip 19.1.1

$ pip show peewee pymysql
Name: peewee
Version: 3.9.6
---
Name: PyMySQL
Version: 0.9.3

詳細

peewee について

「MySQL の操作が〜」と書いたが、peewee 自身は MySQL だけでなく sqlite, postgresql にも対応した ORM である。

DB 接続

peewee.MySQLDatabase で DB に接続する。

1
2
3
4
5
6
7
8
9
import peewee
db = peewee.MySQLDatabase(
database = "peewee_test",
user = "username",
password = "password",
host = "127.0.0.1",
port = 3306,
charset = "utf8mb4"
)

正常に接続できている場合は connect で True が返ってくる。

1
2
db.connect()
# => True

connect_params で、接続時のパラメータが確認できる。

なお、接続時に 'charset' を省略した場合は utf8 になるため、システムによっては問題となる可能性があると思われる。詳しくはこちらを参照。

1
2
db.connect_params
# => {'charset': 'utf8mb4', 'use_unicode': True, 'user': 'username', 'password': 'password', 'host': '127.0.0.1', 'port': 3306}

接続中の DB は database で確認できる。

1
2
db.database
# => 'peewee_test'

テーブル一覧 取得

get_tables でテーブル一覧が取得可能。アルファベット昇順の List で返却される。

1
2
db.get_tables()
# => ['accesses', 'admins', ..., 'users']

テーブル 作成

create_tables 、あるいは create_table でテーブル作成。事前にテーブル(モデル)を class で定義しておく必要がある。

1
2
3
4
5
6
7
8
9
class User(peewee.Model):
name = peewee.CharField()
age = peewee.SmallIntegerField()
class Meta:
database = db

db.create_tables([User]) # or User.create_table() でも可
db.get_tables()
# => ['user']

MySQL 側で見ると以下のような状態となっている。

  • id フィールドはデフォルトで作られる
    • Big Integer ではないので注意(バージョンで 違う/変わる かも)
  • created_at, updated_at などは無い
    • 欲しい場合は peewee.DateTimeField で定義する必要がある
1
2
3
4
5
6
7
8
9
mysql> DESC user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| age | smallint(6) | NO | | NULL | |
+-------+--------------+------+-----+---------+----------------+
3 rows in set (0.09 sec)

テーブル名を複数形にする

単純にクラス名を Users のようにすれば users としてテーブルが作成される。

1
2
3
4
5
6
class Users(peewee.Model):
name = peewee.CharField()
class Meta:
database = db

Users.create_table()
1
2
3
4
5
6
7
mysql> show tables;
+-----------------------+
| Tables_in_peewee_test |
+-----------------------+
| users |
+-----------------------+
1 row in set (0.00 sec)

キャメルケース(パスカルケース)のテーブル名を _(アンダースコア) 区切りにする

UserProfile というクラス名でテーブルを作ると、userprofile というテーブル名になる。

user_profile というテーブル名にしたい場合には、Meta クラスの中で legacy_table_names = False にすると良い。

Table Names

1
2
3
4
5
class UserProfile(peewee.Model):
name = peewee.CharField()
class Meta:
database = db
legacy_table_names = False

テーブル名を自分で設定する

「モデルは単数形のままで、テーブル名は複数形にしたい」とか「キャメルケース(パスカルケース)で、_(アンダースコア)区切りにしたい」とか、細かいこだわりがある場合には、Meta クラスの中で table_name を直接設定すれば OK。

Table Names

1
2
3
4
5
class User(peewee.Model):
name = peewee.CharField()
class Meta:
database = db
table_name = "super_users"

Big Integer で id フィールドを定義

BigAutoFieldprimary_key=True の組合せで定義すれば良い。

1
2
3
4
5
6
7
8
9
class User(peewee.Model):
id = peewee.BigAutoField(primary_key=True)
name = peewee.CharField()
class Meta:
database = db

db.create_tables([User]) # or User.create_table() でも可
db.get_tables()
# => ['user']
1
2
3
4
5
6
7
8
mysql> desc user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
+-------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
ForeignKeyField との連携

Field types table には ForeignKeyFieldInteger と書かれているが、参照先が BigInt なら ForeignKey も BigInt になる。

ForeignField not compatible with BigAutoField · Issue #1630 · coleifer/peewee で対応されている。

利用可能フィールド

Field types table を参照。

ForeignKeyField: 外部キー制約

外部キー制約は ForeignKeyField で定義する。

1
2
3
4
5
6
7
from peewee import *

class Author(Model):
name = CharField()

class Book(Model):
author = ForeignKeyField(User)

親データ削除とともに、子データも消したい場合には on_delete="CASCADE" を設定。デフォルトは RESTRICT なので、子データを消してからじゃないと親データを消せない。

CASCADE? RESTRICT??」って人は MySQLの外部キー制約RESTRICT,CASCADE,SET NULL,NO ACTIONの違いは? - Qiita を参照。

1
2
3
4
5
6
7
from peewee import *

class Author(Model):
name = CharField()

class Book(Model):
author = ForeignKeyField(User, on_delete="CASCADE")

フィールドオプション

デフォルト値の設定や、NOT NULL 制約なども指定可能。

詳細は Field initialization arguments を参照。

デフォルト値

default オプションで指定。

1
2
class User(peewee.Model):
age = peewee.SmallIntegerField(default=20) # 20歳以上向けのサービスなどを想定
NOT NULL 制約

null オプションで指定。

  • True: NULL OK(許容)
  • False: NULL NG(非許容)
1
2
class User(peewee.Model):
comment = peewee.CharField(null=True)
UNIQUE 制約

unique オプションで指定。

  • True: UNIQUE 制約あり
  • False: UNIQUE 制約なし
1
2
class User(peewee.Model):
email = peewee.CharField(unique=True)
複合 UNIQUE 制約

Multi-column indexes を参照

class Meta: の中で定義、複合インデックスにしたいカラムをタプルで渡して、ユニーク制約をかける場合には True を指定する。

1
2
3
4
5
6
7
8
class User(peewee.Model):
name = peewee.CharField()
email = peewee.CharField()

class Meta:
indexes = (
(("name", "email"), True) # name x email => unique index
)

テーブル 削除

drop_tables、あるいは drop_table でテーブル削除。

1
db.drop_tables([User])  # or User.drop_table()

データ 追加

create、あるいは定義したモデルのインスタンス作成 + save でデータ作成。

1
2
3
4
5
6
7
8
9
10
11
12
class User(peewee.Model):
name = peewee.CharField()
age = peewee.SmallIntegerField()
class Meta:
database = db

User.create(name="kenji", age=10)
# => <User: 1>

user = User(name="hanako", age=12)
user.save()
# => 1 ※save に失敗すると何らかの例外が発生する

データ 更新

  • get_by_id などでデータ取得して、
  • where と組み合わせる方法もある
    • User.update({User.name: "hoge"}).where(User.id == 1).execute()
1
2
3
4
5
6
7
user = User.get_by_id(1)
user.name = "koji"
user.save()

user.update(name="takuya").execute()

User.update({User.name: "hoge"}).where(User.id == 1).execute()

その他・メモ

MySQL driver not installed! と出る場合

PyMySql などの MySQL Driver が入ってないと peewee.ImproperlyConfigured: MySQL driver not installed! のエラーで DB 接続できないので注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
db.table_exists("users")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/r17n/.pyenv/versions/anaconda3-5.0.0/lib/python3.6/site-packages/peewee.py", line 3110, in table_exists
return table_name in self.get_tables(schema=schema)
File "/Users/r17n/.pyenv/versions/anaconda3-5.0.0/lib/python3.6/site-packages/peewee.py", line 3770, in get_tables
return [table for table, in self.execute_sql(query, ('VIEW',))]
File "/Users/r17n/.pyenv/versions/anaconda3-5.0.0/lib/python3.6/site-packages/peewee.py", line 2947, in execute_sql
cursor = self.cursor(commit)
File "/Users/r17n/.pyenv/versions/anaconda3-5.0.0/lib/python3.6/site-packages/peewee.py", line 2933, in cursor
self.connect()
File "/Users/r17n/.pyenv/versions/anaconda3-5.0.0/lib/python3.6/site-packages/peewee.py", line 2891, in connect
self._state.set_connection(self._connect())
File "/Users/r17n/.pyenv/versions/anaconda3-5.0.0/lib/python3.6/site-packages/peewee.py", line 3740, in _connect
raise ImproperlyConfigured('MySQL driver not installed!')
peewee.ImproperlyConfigured: MySQL driver not installed!

PyMySql をインストールすることで解決。

1
$ pip install pymysql

参考文献

関連記事