サンプルアプリケーション
シンプルな *Twitter* のようなサイトを構築します。サンプルのソースコードは、examples/twitter
ディレクトリにあります。 githubでソースコードを参照することもできます。また、お好みであればブログアプリの例もありますが、このガイドでは扱いません。
サンプルアプリは、簡単に始められるflaskウェブフレームワークを使用しています。flaskがまだインストールされていない場合は、サンプルを実行するためにインストールする必要があります。
pip install flask
サンプルの実行

flaskがインストールされていることを確認した後、twitterのサンプルディレクトリにcd
で移動し、run_example.py
スクリプトを実行します。
python run_example.py
サンプルアプリは、http://localhost:5000/でアクセスできます。
コードの詳細
簡潔にするために、すべてのサンプルコードは単一のモジュールexamples/twitter/app.py
に含まれています。peeweeを使用した大規模なFlaskアプリの構築方法については、Flaskアプリの構築を参照してください。
モデル
人気のあるWebフレームワークDjangoの精神に基づき、peeweeは宣言型のモデル定義を使用します。Djangoに慣れていない方のために説明すると、各テーブルに対してモデルクラスを宣言するという考え方です。次に、モデルクラスは、テーブルの列に対応する1つ以上のフィールド属性を定義します。Twitterクローンには、3つのモデルしかありません。
- ユーザー:
ユーザーアカウントを表し、ユーザー名とパスワード、*gravatar*を使用してアバターを生成するためのメールアドレス、およびそのアカウントが作成された日時を示す日時フィールドを格納します。
- リレーションシップ:
これは、*User*モデルへの2つの外部キーを含むユーティリティモデルであり、どのユーザーが互いにフォローしているかを格納します。
- メッセージ:
ツイートに類似しています。Messageモデルは、ツイートのテキストコンテンツ、作成日時、投稿者(ユーザーへの外部キー)を格納します。
UMLがお好きなら、これらがテーブルとリレーションシップです。

これらのモデルを作成するには、SqliteDatabase
オブジェクトをインスタンス化する必要があります。次に、モデルクラスを定義し、列をクラスのField
インスタンスとして指定します。
# create a peewee database instance -- our models will use this database to
# persist information
database = SqliteDatabase(DATABASE)
# model definitions -- the standard "pattern" is to define a base model class
# that specifies which database to use. then, any subclasses will automatically
# use the correct storage.
class BaseModel(Model):
class Meta:
database = database
# the user model specifies its fields (or columns) declaratively, like django
class User(BaseModel):
username = CharField(unique=True)
password = CharField()
email = CharField()
join_date = DateTimeField()
# this model contains two foreign keys to user -- it essentially allows us to
# model a "many-to-many" relationship between users. by querying and joining
# on different columns we can expose who a user is "related to" and who is
# "related to" a given user
class Relationship(BaseModel):
from_user = ForeignKeyField(User, backref='relationships')
to_user = ForeignKeyField(User, backref='related_to')
class Meta:
# `indexes` is a tuple of 2-tuples, where the 2-tuples are
# a tuple of column names to index and a boolean indicating
# whether the index is unique or not.
indexes = (
# Specify a unique multi-column index on from/to-user.
(('from_user', 'to_user'), True),
)
# a dead simple one-to-many relationship: one user has 0..n messages, exposed by
# the foreign key. a users messages will be accessible as a special attribute,
# User.messages.
class Message(BaseModel):
user = ForeignKeyField(User, backref='messages')
content = TextField()
pub_date = DateTimeField()
注意
使用するデータベースを定義するだけの*BaseModel*クラスを作成することに注意してください。他のすべてのモデルはこのクラスを拡張し、正しいデータベース接続も使用します。
Peeweeは、データベースエンジンで一般的にサポートされているさまざまな列タイプに対応する、さまざまなフィールドタイプをサポートしています。Pythonの型とデータベースで使用される型の間の変換は透過的に処理されるため、アプリケーションで以下を使用できます。
文字列(Unicodeまたはその他)
整数、浮動小数点数、および
Decimal
数値。ブール値
日付、時刻、および日時
None
(NULL)バイナリデータ
テーブルの作成
モデルを使用するには、テーブルを作成する必要があります。これは1回限りの操作であり、インタラクティブインタープリターを使用してすばやく実行できます。これを実現するための小さなヘルパー関数を作成できます。
def create_tables():
with database:
database.create_tables([User, Relationship, Message])
サンプルアプリと同じディレクトリにあるPythonシェルを開き、以下を実行します。
>>> from app import *
>>> create_tables()
注意
*ImportError*が発生した場合は、*flask*または*peewee*が見つからず、正しくインストールされていない可能性があります。peeweeのインストール手順については、インストールとテストドキュメントを参照してください。
すべてのモデルには、データベースでSQL *CREATE TABLE*ステートメントを実行するcreate_table()
クラスメソッドがあります。このメソッドは、すべての列、外部キー制約、インデックス、およびシーケンスを含むテーブルを作成します。通常、これは新しいモデルが追加されるたびに1回だけ実行します。
Peeweeは、モデル間の依存関係を解決し、各モデルでcreate_table()
を呼び出して、テーブルが順番に作成されるようにするヘルパーメソッドDatabase.create_tables()
を提供します。
注意
テーブルの作成後にフィールドを追加するには、テーブルを削除して再作成するか、*ALTER TABLE*クエリを使用して手動で列を追加する必要があります。
または、スキーママイグレーション拡張機能を使用して、Pythonを使用してデータベーススキーマを変更することもできます。
データベース接続の確立
上記のモデルコードで、database
属性を設定する*Meta*という名前のクラスが基本モデルに定義されていることに気付いたかもしれません。Peeweeでは、すべてのモデルが使用するデータベースを指定できます。モデルの動作を制御する多くのメタオプションを指定できます。
これはpeeweeのイディオムです。
DATABASE = 'tweepee.db'
# Create a database instance that will manage the connection and
# execute queries
database = SqliteDatabase(DATABASE)
# Create a base-class all our models will inherit, which defines
# the database we'll be using.
class BaseModel(Model):
class Meta:
database = database
Webアプリケーションを開発する場合、リクエストの開始時に接続を開き、レスポンスが返されたときに接続を閉じるのが一般的です。**接続は常に明示的に管理する必要があります**。たとえば、接続プールを使用している場合、connect()
とclose()
を呼び出した場合にのみ、接続が正しくリサイクルされます。
リクエスト/レスポンスサイクル中にデータベースへの接続を作成する必要があることをflaskに指示します。Flaskは、これを簡単にするための便利なデコレータを提供しています。
@app.before_request
def before_request():
database.connect()
@app.after_request
def after_request(response):
database.close()
return response
注意
Peeweeはスレッドローカルストレージを使用して接続状態を管理するため、このパターンはマルチスレッドWSGIサーバーで使用できます。
クエリの作成
*User*モデルには、ユーザー固有の機能をカプセル化するインスタンスメソッドがいくつかあります。
following()
: このユーザーは誰をフォローしていますか?followers()
: 誰がこのユーザーをフォローしていますか?
これらのメソッドは実装は似ていますが、SQLの*JOIN*句と*WHERE*句に重要な違いがあります。
def following(self):
# query other users through the "relationship" table
return (User
.select()
.join(Relationship, on=Relationship.to_user)
.where(Relationship.from_user == self)
.order_by(User.username))
def followers(self):
return (User
.select()
.join(Relationship, on=Relationship.from_user)
.where(Relationship.to_user == self)
.order_by(User.username))
新しいオブジェクトの作成
新しいユーザーがサイトに参加したい場合、ユーザー名が使用可能かどうかを確認し、使用可能な場合は新しい*User*レコードを作成する必要があります。*join()*ビューを見ると、アプリケーションがModel.create()
を使用してユーザーを作成しようとしていることがわかります。*User.username*フィールドを一意制約で定義したため、ユーザー名が使用されている場合、データベースはIntegrityError
を発生させます。
try:
with database.atomic():
# Attempt to create the user. If the username is taken, due to the
# unique constraint, the database will raise an IntegrityError.
user = User.create(
username=request.form['username'],
password=md5(request.form['password']).hexdigest(),
email=request.form['email'],
join_date=datetime.datetime.now())
# mark the user as being 'authenticated' by setting the session vars
auth_user(user)
return redirect(url_for('homepage'))
except IntegrityError:
flash('That username is already taken')
ユーザーが誰かをフォローしたい場合も同様のアプローチを使用します。フォロー関係を示すために、*Relationship*テーブルに行を作成し、あるユーザーから別のユーザーを指します。from_user
とto_user
の一意のインデックスにより、重複行が発生しないようにします。
user = get_object_or_404(User, username=username)
try:
with database.atomic():
Relationship.create(
from_user=get_current_user(),
to_user=user)
except IntegrityError:
pass
サブクエリの遂行
ログインしてTwitterのホームページにアクセスすると、フォローしているユーザーのツイートが表示されます。これをきれいに実装するために、サブクエリを使用できます。
注意
サブクエリuser.following()
は、デフォルトでは通常User
モデルのすべての列を選択します。サブクエリとして使用しているため、peeweeは主キーのみを選択します。
# python code
user = get_current_user()
messages = (Message
.select()
.where(Message.user.in_(user.following()))
.order_by(Message.pub_date.desc()))
このコードは、次のSQLクエリに対応します。
SELECT t1."id", t1."user_id", t1."content", t1."pub_date"
FROM "message" AS t1
WHERE t1."user_id" IN (
SELECT t2."id"
FROM "user" AS t2
INNER JOIN "relationship" AS t3
ON t2."id" = t3."to_user_id"
WHERE t3."from_user_id" = ?
)
その他の注目すべきトピック
サンプルアプリには、簡単に説明する価値のある、その他の優れた点がいくつかあります。
結果リストのページネーションのサポートは、
object_list
と呼ばれる単純な関数(Djangoのそれにちなんで名付けられました)に実装されています。この関数は、オブジェクトのリストを返すすべてのビューで使用されます。def object_list(template_name, qr, var_name='object_list', **kwargs): kwargs.update( page=int(request.args.get('page', 1)), pages=qr.count() / 20 + 1) kwargs[var_name] = qr.paginate(kwargs['page']) return render_template(template_name, **kwargs)
login_required
デコレータを使用したシンプルな認証システム。最初の関数は、ユーザーが正常にログインしたときに現在のセッションにユーザーデータを追加するだけです。デコレータlogin_required
は、ビュー関数をラップするために使用でき、セッションが認証されているかどうかを確認し、認証されていない場合はログインページにリダイレクトします。def auth_user(user): session['logged_in'] = True session['user'] = user session['username'] = user.username flash('You are logged in as %s' % (user.username)) def login_required(f): @wraps(f) def inner(*args, **kwargs): if not session.get('logged_in'): return redirect(url_for('login')) return f(*args, **kwargs) return inner
データベースにオブジェクトが見つからない場合は、例外をスローする代わりに 404 レスポンスを返します。
def get_object_or_404(model, *expressions): try: return model.get(*expressions) except model.DoesNotExist: abort(404)
注意
object_list()
や get_object_or_404()
を頻繁にコピー/ペーストする必要がないように、これらの関数は playhouse の Flask 拡張モジュール の一部として含まれています。
from playhouse.flask_utils import get_object_or_404, object_list
その他の例
peewee の examples ディレクトリ には、以下を含むより多くの例が含まれています。
暗号化されたコマンドライン日記。関連ブログ記事もお楽しみいただけます。
注意
これらのスニペットが気に入って、もっと知りたいですか? flask-peewee をチェックしてみてください。これは、Django のような管理インターフェース、RESTful API、認証などを peewee モデルに提供する Flask プラグインです。