【Python】たった15分でマスターできる!Peeweeを使ったシンプルなORM入門

PythonでWebアプリケーションを開発するなら、ORMの使い方を覚えておくと効率的に開発を進められます。
中でもPeeweeは、軽量でシンプルな設計が特徴の人気ORMで、Flaskなどのマイクロフレームワークと相性抜群です。
この記事では、Peeweeの基礎からWebアプリケーション開発の実践的なTipsまでを、サンプルコード付きでわかりやすく解説します。
短時間でPeeweeをマスターして、生産性の高いPythonプログラミングを目指しましょう!

この記事を読んだらわかること
  • ORMの概要とメリット
  • Peeweeのインストールと基本的な使い方
  • モデル定義とクエリの書き方
  • リレーションの定義とCRUD操作
  • トランザクション制御とマイグレーション
  • FlaskなどのフレームワークとのWebアプリ開発
  • パフォーマンスとセキュリティを意識したTips
  • Peeweeのユースケースと学習リソース

Peeweeとは何か?ORM入門

ORMの概要とメリット

ORMとはObject-Relational Mappingの略で、オブジェクト指向プログラミングとリレーショナルデータベースの間を仲介する技術のことです。ORMを使えば、SQLを直接書かなくてもオブジェクト操作感覚でDBを扱うことができます。

ORMを導入するメリットとしては、以下のようなものがあります。

  • コードの可読性と保守性が向上し、開発効率が上がる
  • DBの種類に依存しないため、移行が容易になる
  • SQLインジェクション攻撃のリスクを減らせる

PeeweeというPythonのORM

PeeweeはPythonで書かれた軽量でシンプルなORMライブラリです。Django ORMのようにフルスタックなWebフレームワークに組み込まれているわけではなく、単独のライブラリとして使用できるため、軽量なマイクロフレームワークのFlaskなどと組み合わせやすいのが特徴です。

Peeweeは以下のような主要なRDBに対応しています。

  • SQLite
  • MySQL
  • PostgreSQL

基本的な使い方は、モデルクラスを定義してフィールドを指定し、そのモデルクラスに対して各種操作を行うという流れになります。サンプルコードを見てみましょう。

from peewee import *

# データベースへの接続
db = SqliteDatabase('people.db')

# モデルの定義
class Person(Model):
    name = CharField()
    birthday = DateField()
    is_relative = BooleanField()

    class Meta:
        database = db # 使用するデータベースを指定

# テーブル作成
db.create_tables([Person])

# レコード追加
uncle_bob = Person(name='Bob', birthday=date(1960, 1, 15), is_relative=True) 
uncle_bob.save()

# クエリ
for person in Person.select().where(Person.is_relative == True):
    print(person.name, person.birthday)

このようにモデルを定義してからオブジェクトを通じてCRUDを行えるのがPeeweeの基本スタイルです。クエリもPythonのコードとして表現できるため、SQLを知らなくてもデータベースを操作できるのが嬉しいポイントですね。

他のORMとの比較

Peeweeと他の代表的なPython ORMを比べてみましょう。

Django ORMはDjangoフレームワークに完全統合された形で提供されており、Peeweeよりも抽象度が高い分、Djangoの思想に沿った使い方が求められます。一方のSQLAlchemyは、Peeweeに比べて表現力が高く柔軟ですが、その分学習コストも高くなる傾向にあります。

PonyORMはPeeweeと同じくシンプルさを売りにしていますが、PeeweeはFlaskなどのマイクロフレームワークとの親和性が高いのが特徴です。

ORMにはそれぞれに設計思想の違いがあるので、用途やチームの習熟度に合わせて適切に選択することが大切ですが、Webアプリケーション開発でシンプルさと軽量さを重視するなら、Peeweeは非常に有力な選択肢だと言えるでしょう。

Peeweeの基本的な使い方

インストールとセットアップ

Peeweeはpipコマンドでインストールできます。

pip install peewee

Peewee単体ではデータベースに接続できないため、使用するDBに応じたPythonドライバも別途インストールしておく必要があります。
例えばMySQLを使う場合は、PyMySQLをインストールします。

pip install PyMySQL

インストールが完了したら、Peeweeからデータベースへ接続します。Databaseオブジェクトをインスタンス化する際に、データベース名、ユーザー名、パスワード、ホスト名などの接続情報を指定します。

from peewee import *

db = MySQLDatabase('my_database', user='user', password='password', host='hostname')

モデルの定義方法

PeeweeではModelクラスを継承してモデルを定義します。Userモデルを例に見てみましょう。

from peewee import *

class User(Model):
    username = CharField(unique=True)
    email = CharField(unique=True)
    age = IntegerField()

    class Meta:
        database = db
        table_name = 'users'

フィールドは、CharFieldやIntegerFieldなどの型ごとに用意されているFieldクラスを使って定義します。その際、ユニーク制約やデフォルト値なども指定できます。

また、Metaクラス内で使用するデータベースやテーブル名を指定します。テーブル名を省略した場合は、モデルクラス名の小文字がテーブル名になります。

クエリの書き方

Peeweeでは、selectメソッドを呼び出すことでSELECTクエリを発行できます。

query = User.select()
for user in query:
    print(user.username, user.email)

whereメソッドでフィルタリング条件を、order_byメソッドではソート条件を指定できます。これらはメソッドチェーンでつなげて書くことができます。

query = User.select().where(User.age > 20).order_by(User.username)

count, avg, sumなどの集計関数も使えます。

count = User.select().count()
print(count)

CRUDの操作方法

レコードを追加するにはcreateメソッドを使います。

user = User.create(username='john', email='john@example.com', age=30)

レコードを更新するには、インスタンスの属性を変更してsaveメソッドを呼び出します。

user.age = 31
user.save()

レコードを削除するにはdelete_instanceメソッドを使います。

user.delete_instance()

このようにPeeweeでは、シンプルなクラスとメソッドでデータベース操作が行えます。
次は、もう少し応用的な使い方を見ていきましょう。

Peeweeの応用的な使い方

リレーションの定義方法

リレーショナルデータベースの特徴は、テーブル同士を関連付けられること。Peeweeでは、ForeignKeyFieldやManyToManyFieldを使ってモデル間のリレーションを定義できます。

例えば、ユーザーとツイートの1対多の関係は次のように定義できます。

class User(Model):
    username = CharField()

class Tweet(Model):
    user = ForeignKeyField(User, backref='tweets')
    message = TextField()

# ユーザーの書いたツイートを取得
user = User.get(User.username == 'john')
tweets = user.tweets

# ツイートを書いたユーザーを取得
tweet = Tweet.get()
user = tweet.user

ForeignKeyFieldには、関連付ける対象のモデルクラスと、backrefキーワード引数でユーザー側からアクセスするための属性名を指定します。

多対多の関係には、ManyToManyFieldを使用します。ユーザーがツイートをお気に入りに登録する場合を考えてみましょう。

class User(Model):
    username = CharField()

class Tweet(Model):
    message = TextField()  

class Favorite(Model):
    user = ForeignKeyField(User)
    tweet = ForeignKeyField(Tweet)

# ユーザーのお気に入りツイートを取得
user = User.get(User.username == 'john')
favorites = Favorite.select().where(Favorite.user == user)
tweets = [fav.tweet for fav in favorites]

# ツイートをお気に入りしたユーザーを取得  
tweet = Tweet.get()
favorites = Favorite.select().where(Favorite.tweet == tweet)
users = [fav.user for fav in favorites]  

中間モデルのFavoriteを定義し、それぞれのForeignKeyFieldでUserとTweetに関連付けることで多対多のリレーションを表現しています。

このように、Peeweeではシンプルなフィールド定義でモデル間の関係性を表すことができます。通常のSQLでは複雑なJOINクエリを書かなければいけませんが、Peeweeでは関連オブジェクトを辿るだけでデータを取得できるのがポイントですね。

トランザクション処理

トランザクションとは、データベースへの一連の変更操作を1つの単位としてまとめるための仕組みです。

トランザクション中に例外やエラーが発生したら、それまでの変更をなかったことにできます。Peeweeでは、atomicコンテキストマネージャでトランザクションを制御できます。

with db.atomic() as transaction:  
    try:
        user = User.create(username='john')
        Tweet.create(user=user, message='Hello')
    except IntegrityError:
        # 一意制約エラーなどの場合ロールバック
        transaction.rollback()
    else:  
        # 処理が成功したらコミット
        transaction.commit()

このコードは、ユーザーを作成しツイートを追加する処理をトランザクションとしてまとめています。

例外が発生したらロールバックし、データベースを変更前の状態に戻します。elseブロックまで到達できれば、commitでトランザクションの結果を確定します。

atomicブロック内でネストしたトランザクションを作ることもでき、柔軟な制御が可能です。

マイグレーション管理

開発を進めていくとモデルのフィールドを変更したくなることがあります。その際、変更内容に合わせてデータベーススキーマも修正する必要がありますが、それを管理するのがマイグレーションです。

Peeweeにはpwizというユーティリティが付属しており、コマンド一発でマイグレーションファイルを作成してくれます。

$ python -m pwiz -e mysql -u user -P password my_database > migration.py  

出力されるマイグレーションファイルは、モデルとDBのスキーマ差分を表すPythonスクリプトになっています。自動生成されたスクリプトを確認し、migrateとrollbackの関数を必要に応じて修正します。

from peewee import *

def migrate(migrator, database, **kwargs):
    # usersテーブルにageカラムを追加
    migrator.add_column('users', 'age', IntegerField(null=True))

def rollback(migrator, database, **kwargs):
    # usersテーブルのageカラムを削除
    migrator.drop_column('users', 'age')

修正が完了したら、migrateコマンドでマイグレーションを実行します。

$ python migration.py

必要に応じてrollbackも実行でき、モデルとデータベースのスキーマを常に同期させられるのがマイグレーションの利点ですね。

応用的な機能も一通り押さえたところで、次はいよいよWebアプリケーション開発について見ていきましょう。

Peeweeを使ったWebアプリケーション開発

Flask等のフレームワークとの連携

PythonのWebフレームワークの中でもシンプルで使いやすいFlaskは、PeeweeのORMと非常に相性が良いです。flask-peewee拡張を使えば、FlaskアプリケーションでPeeweeのデータベース接続を一元管理できるようになります。

from flask import Flask
from flask_peewee.db import Database

app = Flask(__name__)
app.config.from_object('config.Configuration')
db = Database(app)

class User(db.Model):
    username = CharField(unique=True)

@app.route('/users/<username>')
def user_detail(username):
    user = User.get(User.username == username)
    return render_template('user_detail.html', user=user)

アプリケーションのconfigで接続情報を設定した上で、Databaseクラスにappインスタンスを渡すとデータベース接続が作成されます。あとはルーティングで指定したエンドポイントのビュー関数内で、通常のPeeweeモデルと同じようにクエリを発行するだけ。

ビュー関数からテンプレートにモデルオブジェクトを渡せば、htmlからもモデルのプロパティにアクセスできるので、データベース駆動のWebアプリケーションをスムーズに開発できますね。

アプリケーションのアーキテクチャ設計

PeeweeとFlaskを使ったWebアプリケーションのアーキテクチャは、次のような構成になるのが一般的です。

  • モデル層: Peeweeのモデルクラスを定義
  • ビュー層: FlaskのBlueprint・ビュー関数を記述
  • テンプレート層: Jinja2テンプレートで表示を制御
  • コントローラ層: アプリケーションのビジネスロジックを記述

コントローラにメインのロジックを書き、必要に応じてPeeweeモデルをビュー関数に渡すことで、MVCライクな構成にできます。FlaskのBlueprintを使えば機能ごとにルーティングを定義できるので、規模の大きなアプリケーションでもメンテナンス性を保ちやすくなります。

データベース接続は、アプリケーション内の複数の箇所から利用するため、シングルトンのオブジェクトで一元管理するのがおすすめです。

テスト駆動開発との相性

Webアプリケーションの品質を保つためには、自動テストが欠かせません。Peeweeを使ったアプリケーションも例外ではありません。

まずモデル層の単体テストから見ていきましょう。テスト用のデータベースを用意しておき、各テストケースを実行するごとに初期データをセットアップします。

def test_user_creation(self):
    with test_database.transaction() as txn:
        User.create(username='john')
        self.assertEqual(User.select().count(), 1)
        txn.rollback()

test_databaseはテスト用に用意したデータベース接続です。テストケース内でトランザクションを開始し、検証が終わったらロールバックすることで、各テストを独立して実行できます。

コントローラとビューのテストには、Flaskのテストクライアントを使います。

def test_user_detail(self):
    User.create(username='john')
    response = self.app.get('/users/john')
    self.assertIn('john', response.data)

POSTリクエストのテストも、同じように実行できますね。

モデル層の処理をコントローラに閉じ込めておけば、外部からはモックオブジェクトに差し替えてテストを書くことができます。Mockitoなどのモックライブラリを使えば、Peeweeのクエリ発行をシミュレートしながらコントローラのロジックをテストできるでしょう。

Peeweeのシンプルで直感的なインターフェイスのおかげで、フレームワークと組み合わせたテスト駆動開発もスムーズに進められそうです。

次は実際のアプリケーションを作るときのTipsを見ていきましょう。

Peeweeのベストプラクティス

コード例で学ぶ実践的な使い方

Peeweeを使いこなすには、モデル定義とクエリの書き方のコツを押さえておくことが大切です。

まずモデル定義では以下の点に注意しましょう。

  • フィールド名はわかりやすいものを選ぶ。略語を使わず、明示的な名前をつける。
  • データ型に合わせて、デフォルト値やNULL制約、インデックスを適切に設定する。
  • よく使うクエリはクラスメソッドとして定義しておくと便利。
class User(Model):
    username = CharField(unique=True)
    email = CharField(index=True)
    is_active = BooleanField(default=True)

    @classmethod
    def active_users(cls):
        return cls.select().where(cls.is_active == True)

クエリを書くときは、selectで取得するフィールドを絞り込むようにしましょう。

User.select(User.username).join(Tweet).where(Tweet.content.contains('Peewee'))

関連テーブルのデータも一緒に取得したいときは、eagerロードを使うと効率的です。

Tweet.select(Tweet, User).join(User).where(User.username=='john')

INやEXISTSを使えば、部分一致検索のパフォーマンスを改善できます。

usernames = ['john', 'paul', 'george', 'ringo']
User.select().where(User.username.in_(usernames))

パフォーマンスを意識したクエリの書き方

パフォーマンスチューニングでは、まずクエリの実行計画を確認しましょう。

query = Tweet.select() 
print(query.explain())

実行計画を見ると、インデックスが有効に使われているかがわかります。

複雑なクエリは、ウィンドウ関数や共通テーブル式を使って分割すると読みやすくなります。

subquery = Tweet.select(
    Tweet.user,  
    fn.COUNT(Tweet.id).over(partition_by=[Tweet.user]).alias('count')
).alias('subquery')

query = (User
         .select(User, subquery.c.count)
         .join(subquery, on=(User.id == subquery.c.user_id)))  

大量のレコードを更新するときは、バルクアップデートを使いましょう。

Tweet.update(content='').where(User.username=='john').execute()

トランザクション分離レベルを適切に設定することで、ロック競合を防げます。

セキュリティに配慮したコーディング

セキュリティ対策の基本は、ユーザーからの入力をバリデーションすることです。
不正な値は、エスケープ処理を行いましょう。

SQLインジェクションは、プレースホルダを使うことで防げます。

def login(username, password):
    try:
        user = User.get(User.username == username)
        if check_password(password, user.password_hash):
            return user  
    except User.DoesNotExist:
        pass

パスワードは、単方向のハッシュ関数でハッシュ化してから保存します。

def encrypt_password(password):
    return bcrypt.hashpw(password, bcrypt.gensalt())

重要なデータは、SSLで暗号化して通信します。

使う側に便利なインターフェイスを提供しつつ、内部ではセキュアなロジックを隠蔽する。
これこそがPeeweeのようなORMツールに求められる役割だと言えるでしょう。

最後に、Peeweeの使いどころをまとめつつ、チュートリアルの締めくくりとしましょう。

まとめ:Peeweeを使いこなそう!

Peeweeのメリットと向いているケース

Peeweeの最大の魅力は、SQLを直接書かずにPythonのコードでデータベースを操作できることです。
オブジェクト指向プログラミングの感覚でリレーショナルデータベースを扱えるので、開発者にとって理解しやすく、コードの生産性も上がります。

Peeweeは、シンプルで直感的なAPIを提供しているため、学習コストが低いのも大きな利点です。
ORMを使った開発の経験が少ないチームでも、短期間で導入できるでしょう。

FlaskなどのマイクロフレームワークとPeeweeの相性の良さは、小〜中規模のWebアプリケーション開発で特に役立ちます。
機械学習のバックエンドやデータ集約サービスのAPIなど、データベースを活用するアプリケーションを素早く立ち上げることができます。

from flask import Flask
from flask_peewee.db import Database
from peewee import *

app = Flask(__name__)
db = Database(app)

class User(db.Model):
    username = CharField()
    email = CharField()

@app.route('/')
def index():
    users = User.select()
    return render_template('index.html', users=users)

Djangoのようなフルスタックフレームワークと比べると、Peeweeはマイグレーションやモデル定義の自動生成などの機能を標準で持たないため、その分開発者側での設計や実装が必要になります。
しかし、プロジェクトのアーキテクチャを自由に決められるというメリットもあるので、要件に合わせて柔軟に対応できるでしょう。

さらに学習を進めるためのリソース

Peeweeには、公式ドキュメントやGitHubのリポジトリに豊富なリソースがあります。

公式ドキュメントには、APIリファレンスだけでなく、クイックスタートやチュートリアルもあるので、体系的に学習を進められます。
http://docs.peewee-orm.com/

GitHubのリポジトリでは、Peeweeのソースコードを読んで内部構造を理解することができます。
また、examplesディレクトリには、数多くのサンプルコードがあるので、実際の使い方を学ぶのにも役立つでしょう。
https://github.com/coleifer/peewee

書籍では、『Making Queries with Peewee ORM』がおすすめです。
Peeweeの基本的な使い方から応用的なテクニックまでを網羅的に解説しているので、実践的なスキルを身につけられます。

Flaskと連携してWebアプリケーションを開発するなら、Flask-Peeweeというスターターキットを使ってみるのも良いでしょう。
認証機能やユーザー管理、データベース設定などの基本的な機能が揃っているので、すぐに開発を始められます。
https://github.com/coleifer/flask-peewee

Peeweeを使った実践的なアプリケーション開発のノウハウを学ぶなら、オープンソースのプロジェクトを参考にするのがおすすめです。
例えば、Peeweeの作者が開発しているタスクキューのHueyは、Peeweeでデータモデルを定義し、ワーカープロセスを管理するアプリケーションの良い見本になります。

このチュートリアルを通して、Peeweeの基本的な使い方から応用的な機能、実践的なノウハウまでを一通り学んでいただけたかと思います。
PeeweeでPythonライクなコードを書きながら、SQLの知識も深めていけば、より堅牢で効率的なデータベースアプリケーションが作れるようになるでしょう。
ぜひ開発現場で活用してみてください。