クイックスタート
このドキュメントでは、Peeweeの主要機能の概要を簡潔に説明します。このガイドでは、以下の内容を扱います。
注記
もう少し具体的な内容をご希望の場合は、peeweeとFlaskフレームワークを使用して「Twitter」スタイルのWebアプリを作成する詳細なチュートリアルがあります。プロジェクトのexamples/
フォルダには、ブログアプリなど、より自己完結型のPeeweeの例があります。
インタラクティブシェルセッションを開いてコードを実行することを強くお勧めします。そうすることで、クエリを入力する感覚をつかむことができます。
モデル定義
モデルクラス、フィールド、モデルインスタンスはすべてデータベースの概念に対応しています。
オブジェクト |
対応するもの… |
---|---|
モデルクラス |
データベーステーブル |
フィールドインスタンス |
テーブルの列 |
モデルインスタンス |
データベーステーブルの行 |
peeweeでプロジェクトを開始する際には、通常、1つ以上のModel
クラスを定義することから始めます。
from peewee import *
db = SqliteDatabase('people.db')
class Person(Model):
name = CharField()
birthday = DateField()
class Meta:
database = db # This model uses the "people.db" database.
注記
Peeweeは、クラス名からデータベーステーブル名を自動的に推測します。内部の「Meta」クラス(database
属性と共に)でtable_name
属性を指定することで、デフォルト名をオーバーライドできます。Peeweeがテーブル名を生成する方法の詳細については、テーブル名のセクションを参照してください。
また、モデルにPeople
ではなくPerson
という名前を付けたことにも注意してください。これは、テーブルには複数の人が含まれていても、常に単数形を使用してクラスに名前を付けるという、従うべき規則です。
さまざまなタイプのデータを格納するのに適したフィールドタイプはたくさんあります。Peeweeは、Python的な値とデータベースで使用される値との間の変換を処理するため、心配することなくコードでPythonの型を使用できます。
外部キーリレーションシップを使用してモデル間のリレーションシップを設定すると、面白くなります。これはpeeweeでは簡単です。
class Pet(Model):
owner = ForeignKeyField(Person, backref='pets')
name = CharField()
animal_type = CharField()
class Meta:
database = db # this model uses the "people.db" database
モデルができたので、データベースに接続しましょう。明示的に接続を開く必要はありませんが、最初のクエリが実行されたときに任意の時間にエラーが発生するのではなく、データベース接続のエラーがすぐに明らかになるため、良い習慣です。また、作業が終わったら接続を閉じることも良いことです。たとえば、Webアプリはリクエストを受信したときに接続を開き、レスポンスを送信したときに接続を閉じることがあります。
db.connect()
まず、データを格納するデータベースにテーブルを作成します。これにより、適切な列、インデックス、シーケンス、および外部キー制約を持つテーブルが作成されます。
db.create_tables([Person, Pet])
データの保存
まず、データベースに何人かの人を追加することから始めましょう。save()
メソッドとcreate()
メソッドを使用して、人物のレコードを追加および更新します。
from datetime import date
uncle_bob = Person(name='Bob', birthday=date(1960, 1, 15))
uncle_bob.save() # bob is now stored in the database
# Returns: 1
注記
save()
を呼び出すと、変更された行数が返されます。
create()
メソッドを呼び出して人物を追加することもできます。このメソッドはモデルインスタンスを返します。
grandma = Person.create(name='Grandma', birthday=date(1935, 3, 1))
herb = Person.create(name='Herb', birthday=date(1950, 5, 5))
行を更新するには、モデルインスタンスを変更し、save()
を呼び出して変更を永続化します。ここでは、おばあちゃんの名前を変更し、データベースに変更を保存します。
grandma.name = 'Grandma L.'
grandma.save() # Update grandma's name in the database.
# Returns: 1
これで、データベースに3人が保存されました。彼らにペットをあげましょう。おばあちゃんは家の中に動物がいるのが好きではないので、ペットはいませんが、ハーブは動物好きです。
bob_kitty = Pet.create(owner=uncle_bob, name='Kitty', animal_type='cat')
herb_fido = Pet.create(owner=herb, name='Fido', animal_type='dog')
herb_mittens = Pet.create(owner=herb, name='Mittens', animal_type='cat')
herb_mittens_jr = Pet.create(owner=herb, name='Mittens Jr', animal_type='cat')
長く充実した人生を送った後、ミトンズは病気になり、亡くなりました。彼をデータベースから削除する必要があります。
herb_mittens.delete_instance() # he had a great life
# Returns: 1
注記
delete_instance()
の戻り値は、データベースから削除された行数です。
ボブおじさんは、ハーブの家で死にすぎた動物が多すぎると判断し、ファイアーを養子にします。
herb_fido.owner = uncle_bob
herb_fido.save()
データの取得
データベースの真の強みは、*クエリ*を通じてデータを取得できることです。リレーショナルデータベースは、アドホッククエリを作成するのに優れています。
単一レコードの取得
データベースからおばあちゃんのレコードを取得してみましょう。データベースから単一のレコードを取得するには、Select.get()
を使用します。
grandma = Person.select().where(Person.name == 'Grandma L.').get()
同等の省略形であるModel.get()
を使用することもできます。
grandma = Person.get(Person.name == 'Grandma L.')
レコードのリスト
データベース内のすべての人をリストしてみましょう。
for person in Person.select():
print(person.name)
# prints:
# Bob
# Grandma L.
# Herb
すべての猫とその飼い主の名前をリストしてみましょう。
query = Pet.select().where(Pet.animal_type == 'cat')
for pet in query:
print(pet.name, pet.owner.name)
# prints:
# Kitty Bob
# Mittens Jr Herb
注意
前のクエリには大きな問題があります。pet.owner.name
にアクセスしていて、元のクエリでこのリレーションを選択しなかったため、peeweeはペットの飼い主を取得するために追加のクエリを実行する必要があります。この動作はN+1問題と呼ばれ、一般的に避けるべきです。
リレーションシップと結合の操作に関する詳細なガイドについては、リレーションシップと結合のドキュメントを参照してください。
*Pet*と*Person*の両方を選択し、*結合*を追加することで、余分なクエリを回避できます。
query = (Pet
.select(Pet, Person)
.join(Person)
.where(Pet.animal_type == 'cat'))
for pet in query:
print(pet.name, pet.owner.name)
# prints:
# Kitty Bob
# Mittens Jr Herb
ボブが所有しているすべてのペットを取得しましょう。
for pet in Pet.select().join(Person).where(Person.name == 'Bob'):
print(pet.name)
# prints:
# Kitty
# Fido
ボブのペットを取得するために、ここで別のクールなことができます。ボブを表すオブジェクトが既にあるので、代わりにこれを行うことができます。
for pet in Pet.select().where(Pet.owner == uncle_bob):
print(pet.name)
ソート
order_by()
句を追加して、アルファベット順にソートされていることを確認しましょう。
for pet in Pet.select().where(Pet.owner == uncle_bob).order_by(Pet.name):
print(pet.name)
# prints:
# Fido
# Kitty
すべての人を年齢の若い順にリストしてみましょう。
for person in Person.select().order_by(Person.birthday.desc()):
print(person.name, person.birthday)
# prints:
# Bob 1960-01-15
# Herb 1950-05-05
# Grandma L. 1935-03-01
フィルター式の組み合わせ
Peeweeは、任意のネストされた式をサポートしています。誕生日が次のいずれかであるすべての人を取得しましょう。
1940年より前(おばあちゃん)
1959年より後(ボブ)
d1940 = date(1940, 1, 1)
d1960 = date(1960, 1, 1)
query = (Person
.select()
.where((Person.birthday < d1940) | (Person.birthday > d1960)))
for person in query:
print(person.name, person.birthday)
# prints:
# Bob 1960-01-15
# Grandma L. 1935-03-01
今度は逆のことをしてみましょう。誕生日が1940年から1960年の間の人(両年を含む)
query = (Person
.select()
.where(Person.birthday.between(d1940, d1960)))
for person in query:
print(person.name, person.birthday)
# prints:
# Herb 1950-05-05
集計とプリフェッチ
すべての人と、彼らが飼っているペットの数をリストしてみましょう。
for person in Person.select():
print(person.name, person.pets.count(), 'pets')
# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets
ここでも、N+1問題の典型的な例に遭遇しました。この場合、元のSELECT
によって返されたすべてのPerson
に対して追加のクエリを実行しています。*JOIN*を実行し、SQL関数を使用して結果を集計することで、これを回避できます。
query = (Person
.select(Person, fn.COUNT(Pet.id).alias('pet_count'))
.join(Pet, JOIN.LEFT_OUTER) # include people without pets.
.group_by(Person)
.order_by(Person.name))
for person in query:
# "pet_count" becomes an attribute on the returned model instances.
print(person.name, person.pet_count, 'pets')
# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets
注記
Peeweeは魔法のヘルパーfn()
を提供します。これは、任意のSQL関数を呼び出すために使用できます。上記の例では、fn.COUNT(Pet.id).alias('pet_count')
はCOUNT(pet.id) AS pet_count
に変換されます。
すべての人と、彼らのすべてのペットの名前をリストしてみましょう。ご想像のとおり、注意しないと、これは簡単に別のN+1問題の状況になる可能性があります。
コードに飛び込む前に、この例が、すべてのペットとその飼い主の名前をリストした earlier の例とどのように異なるかを考えてみましょう。ペットは1人の飼い主しか持つことができないため、Pet
からPerson
に結合を実行したとき、常に単一の一致がありました。Person
からPet
に結合する場合、状況は異なります。なぜなら、人はペットを0匹持っているか、複数匹持っている可能性があるからです。リレーショナルデータベースを使用しているため、Person
からPet
に結合を行うと、複数のペットを持つすべての人が、ペットごとに1回繰り返されます。
次のようになります。
query = (Person
.select(Person, Pet)
.join(Pet, JOIN.LEFT_OUTER)
.order_by(Person.name, Pet.name))
for person in query:
# We need to check if they have a pet instance attached, since not all
# people have pets.
if hasattr(person, 'pet'):
print(person.name, person.pet.name)
else:
print(person.name, 'no pets')
# prints:
# Bob Fido
# Bob Kitty
# Grandma L. no pets
# Herb Mittens Jr
通常、この種の重複は望ましくありません。人物をリストし、その人物のペットの**リスト**を添付するという、より一般的で直感的なワークフローに対応するために、prefetch()
と呼ばれる特別なメソッドを使用できます。
query = Person.select().order_by(Person.name).prefetch(Pet)
for person in query:
print(person.name)
for pet in person.pets:
print(' *', pet.name)
# prints:
# Bob
# * Kitty
# * Fido
# Grandma L.
# Herb
# * Mittens Jr
SQL関数
最後のクエリです。これはSQL関数を使用して、名前が大文字または小文字の*G*で始まるすべての人物を検索します。
expression = fn.Lower(fn.Substr(Person.name, 1, 1)) == 'g'
for person in Person.select().where(expression):
print(person.name)
# prints:
# Grandma L.
これは基本に過ぎません!クエリは必要に応じて複雑にすることができます。詳細については、クエリに関するドキュメントを参照してください。
データベース
データベースの操作が完了したので、接続を閉じましょう。
db.close()
実際のアプリケーションでは、データベース接続のライフタイムを管理する方法について、いくつかの確立されたパターンがあります。たとえば、Webアプリケーションは通常、リクエストの開始時に接続を開き、レスポンスの生成後に接続を閉じます。コネクションプールは、起動コストに関連するレイテンシを解消するのに役立ちます。
データベースのセットアップについては、多くの例を提供するデータベースドキュメントを参照してください。Peeweeは、実行時のデータベースの設定と、データベースのいつでもの設定または変更もサポートしています。
既存のデータベースの操作
既にデータベースがある場合は、モデルジェネレーターであるpwizを使用して、Peeweeモデルを自動生成できます。たとえば、*charles_blog*という名前のPostgreSQLデータベースがある場合、次のように実行できます。
python -m pwiz -e postgresql charles_blog > blog_models.py
次は?
クイックスタートは以上です。完全なWebアプリを確認したい場合は、サンプルアプリをご覧ください。