モデルとフィールド
Model
クラス、Field
インスタンス、およびモデルインスタンスはすべて、データベースの概念にマッピングされます。
概念 |
対応するもの... |
---|---|
Model クラス |
データベーステーブル |
Field インスタンス |
テーブルの列 |
Model インスタンス |
データベーステーブルの行 |
次のコードは、データベース接続とモデルクラスを定義する典型的な方法を示しています。
import datetime
from peewee import *
db = SqliteDatabase('my_app.db')
class BaseModel(Model):
class Meta:
database = db
class User(BaseModel):
username = CharField(unique=True)
class Tweet(BaseModel):
user = ForeignKeyField(User, backref='tweets')
message = TextField()
created_date = DateTimeField(default=datetime.datetime.now)
is_published = BooleanField(default=True)
Database
のインスタンスを作成します。db = SqliteDatabase('my_app.db')
db
オブジェクトは、Sqliteデータベースへの接続を管理するために使用されます。この例では、SqliteDatabase
を使用していますが、他のデータベースエンジンのいずれかを使用することもできます。データベースを指定する基本モデルクラスを作成します。
モデルクラスを定義します。
class User(BaseModel): username = CharField(unique=True)
モデル定義は、SQLAlchemyやDjangoなどの他の一般的なORMに見られる宣言型スタイルを使用します。BaseModelクラスを拡張しているため、Userモデルはデータベース接続を継承することに注意してください。
一意制約を持つ単一のusername列を明示的に定義しました。主キーを指定していないため、peeweeは自動的にidという名前の自動インクリメント整数主キーフィールドを追加します。
注意
既存のデータベースでpeeweeの使用を開始したい場合は、モデル定義を自動的に生成するpwiz(モデルジェネレーター)を使用できます。
フィールド
Field
クラスは、Model
属性からデータベース列へのマッピングを記述するために使用されます。各フィールド型には対応するSQLストレージクラス(つまり、varchar、int)があり、pythonデータ型と基になるストレージ間の変換は透過的に処理されます。
Model
クラスを作成する場合、フィールドはクラス属性として定義されます。これは、djangoフレームワークのユーザーにはおなじみのはずです。以下に例を示します。
class User(Model):
username = CharField()
join_date = DateTimeField()
about_me = TextField()
上記の例では、フィールドのいずれもprimary_key=True
で初期化されていないため、自動インクリメント主キーが自動的に作成され、「id」という名前が付けられます。PeeweeはAutoField
を使用して自動インクリメント整数主キーを示します。これはprimary_key=True
を意味します。
直感的な方法でモデル間の外部キーリレーションシップを表すことができる特別なフィールド型が1つあります。ForeignKeyField
です。
class Message(Model):
user = ForeignKeyField(User, backref='messages')
body = TextField()
send_date = DateTimeField(default=datetime.datetime.now)
これにより、次のようなコードを作成できます。
>>> print(some_message.user.username)
Some User
>>> for message in some_user.messages:
... print(message.body)
some message
another message
yet another message
注意
外部キー、結合、モデル間のリレーションシップの詳細については、リレーションシップと結合ドキュメントを参照してください。
フィールドの完全なドキュメントについては、フィールドAPIノートを参照してください。
フィールド型テーブル
フィールド型 |
Sqlite |
Postgresql |
MySQL |
---|---|---|---|
|
integer |
serial |
integer |
|
integer |
bigserial |
bigint |
|
integer |
integer |
integer |
|
integer |
bigint |
bigint |
|
integer |
smallint |
smallint |
|
サポートされていません |
int identity |
サポートされていません |
|
real |
real |
real |
|
real |
double precision |
double precision |
|
decimal |
numeric |
numeric |
|
varchar |
varchar |
varchar |
|
char |
char |
char |
|
text |
text |
text |
|
blob |
bytea |
blob |
|
integer |
bigint |
bigint |
|
blob |
bytea |
blob |
|
text |
uuid |
varchar(40) |
|
blob |
bytea |
varbinary(16) |
|
datetime |
timestamp |
datetime |
|
date |
date |
date |
|
time |
time |
time |
|
integer |
integer |
integer |
|
integer |
bigint |
bigint |
|
integer |
boolean |
bool |
|
型なし |
サポートされていません |
サポートされていません |
|
integer |
integer |
integer |
フィールドの初期化引数
すべてのフィールド型で受け入れられるパラメータとそのデフォルト値
null = False
– null値を許可しますindex = False
– この列にインデックスを作成しますunique = False
– この列に一意のインデックスを作成します。また、複合インデックスの追加も参照してください。column_name = None
– データベース内の列名を明示的に指定します。default = None
– 初期化されていないモデルのデフォルトとして使用する任意の値または呼び出し可能オブジェクトprimary_key = False
– テーブルの主キーconstraints = None
- 1つ以上の制約。例:[Check('price > 0')]
sequence = None
– シーケンス名(バックエンドがサポートしている場合)collation = None
– フィールド/インデックスの順序付けに使用する照合順序unindexed = False
– 仮想テーブルのフィールドをインデックスなしにする必要があることを示します(SQLiteのみ)choices = None
– オプションのイテラブル。value
、display
の2タプルが含まれますhelp_text = None
– このフィールドの役立つテキストを表す文字列verbose_name = None
– このフィールドの「ユーザーフレンドリーな」名前を表す文字列index_type = None
– カスタムインデックスタイプを指定します。たとえば、Postgresの場合は、'BRIN'
または'GIN'
インデックスを指定できます。
いくつかのフィールドは特別なパラメータを取ります…
フィールド型 |
特別なパラメータ |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意
default
とchoices
の両方は、それぞれDEFAULTとCHECK CONSTRAINTとしてデータベースレベルで実装できますが、アプリケーションの変更にはスキーマの変更が必要になります。このため、default
は純粋にPythonで実装され、choices
は検証されませんが、メタデータのみを目的として存在します。
データベース(サーバー側)の制約を追加するには、constraints
パラメータを使用します。
フィールドのデフォルト値
Peeweeは、オブジェクトが作成されるときにフィールドのデフォルト値を提供できます。たとえば、IntegerField
のデフォルトをNULL
ではなくゼロにするには、デフォルト値を持つフィールドを宣言できます。
class Message(Model):
context = TextField()
read_count = IntegerField(default=0)
場合によっては、デフォルト値を動的にすることが理にかなうことがあります。一般的なシナリオは、現在の日時を使用することです。Peeweeでは、このような場合に、オブジェクトが作成されるときに返り値が使用される関数を指定できます。関数を提供するだけで、実際に呼び出すわけではないことに注意してください。
class Message(Model):
context = TextField()
timestamp = DateTimeField(default=datetime.datetime.now)
注意
ミュータブルな型(list、dictなど)を受け入れるフィールドを使用し、デフォルトを提供したい場合は、複数のモデルインスタンスが同じ基になるオブジェクトへの参照を共有しないように、デフォルト値を単純な関数でラップすることをお勧めします。
def house_defaults():
return {'beds': 0, 'baths': 0}
class House(Model):
number = TextField()
street = TextField()
attributes = JSONField(default=house_defaults)
データベースも、フィールドのデフォルト値を提供できます。Peeweeは、サーバー側のデフォルト値を設定するためのAPIを明示的に提供していませんが、constraints
パラメータを使用してサーバーのデフォルトを指定できます。
class Message(Model):
context = TextField()
timestamp = DateTimeField(constraints=[SQL('DEFAULT CURRENT_TIMESTAMP')])
注意
注意:default
パラメータを使用する場合、値は実際のテーブルと列の定義の一部ではなく、Peeweeによって設定されます。
ForeignKeyField
ForeignKeyField
は、あるモデルが別のモデルを参照できるようにする特別なフィールド型です。通常、外部キーには、関連するモデルの主キーが含まれます(ただし、field
を指定して特定の列を指定できます)。
外部キーを使用すると、データを正規化できます。モデルの例では、Tweet
からUser
への外部キーがあります。これは、すべてのユーザーが独自のテーブルに格納され、ツイートも同様に格納され、ツイートからユーザーへの外部キーを使用すると、各ツイートが特定のユーザーオブジェクトを指すことができることを意味します。
注意
外部キー、結合、モデル間の関係の詳細については、リレーションシップと結合ドキュメントを参照してください。
peeweeでは、ForeignKeyField
の値にアクセスすると、関連するオブジェクト全体が返されます。例:
tweets = (Tweet
.select(Tweet, User)
.join(User)
.order_by(Tweet.created_date.desc()))
for tweet in tweets:
print(tweet.user.username, tweet.message)
注意
上記の例では、User
データがクエリの一部として選択されました。この手法の例については、N+1の回避ドキュメントを参照してください。
ただし、User
を選択しなかった場合、関連するUser
データをフェッチするために追加のクエリが発行されます。
tweets = Tweet.select().order_by(Tweet.created_date.desc())
for tweet in tweets:
# WARNING: an additional query will be issued for EACH tweet
# to fetch the associated User data.
print(tweet.user.username, tweet.message)
外部キー列から関連する主キー値のみが必要な場合があります。この場合、Peeweeは、Djangoによって確立された規約に従い、外部キーフィールドの名前に"_id"
を追加することにより、生の外部キー値にアクセスできるようにします。
tweets = Tweet.select()
for tweet in tweets:
# Instead of "tweet.user", we will just get the raw ID value stored
# in the column.
print(tweet.user_id, tweet.message)
外部キーを誤って解決して追加のクエリをトリガーするのを防ぐために、ForeignKeyField
は、lazy_load
という初期化パラメータをサポートしており、無効にすると"_id"
属性のように動作します。例:
class Tweet(Model):
# ... same fields, except we declare the user FK to have
# lazy-load disabled:
user = ForeignKeyField(User, backref='tweets', lazy_load=False)
for tweet in Tweet.select():
print(tweet.user, tweet.message)
# With lazy-load disabled, accessing tweet.user will not perform an extra
# query and the user ID value is returned instead.
# e.g.:
# 1 tweet from user1
# 1 another from user1
# 2 tweet from user2
# However, if we eagerly load the related user object, then the user
# foreign key will behave like usual:
for tweet in Tweet.select(Tweet, User).join(User):
print(tweet.user.username, tweet.message)
# user1 tweet from user1
# user1 another from user1
# user2 tweet from user1
ForeignKeyFieldの逆参照
ForeignKeyField
を使用すると、逆参照プロパティをターゲットモデルにバインドできます。暗黙的に、このプロパティはclassname_set
という名前になります。classname
はクラスの小文字名ですが、パラメータbackref
を使用して上書きできます。
class Message(Model):
from_user = ForeignKeyField(User, backref='outbox')
to_user = ForeignKeyField(User, backref='inbox')
text = TextField()
for message in some_user.outbox:
# We are iterating over all Messages whose from_user is some_user.
print(message)
for message in some_user.inbox:
# We are iterating over all Messages whose to_user is some_user
print(message)
DateTimeField、DateField、TimeField
日付と時刻を操作するための3つのフィールドには、年、月、時などにアクセスできる特別なプロパティがあります。
DateField
には、次のプロパティがあります。
year
month
day
TimeField
には、次のプロパティがあります。
hour
minute
second
DateTimeField
には、上記のすべてがあります。
これらのプロパティは、他の式と同じように使用できます。イベントカレンダーがあり、イベントが添付されている現在の月のすべての日を強調表示したいとしましょう。
# Get the current time.
now = datetime.datetime.now()
# Get days that have events for the current month.
Event.select(Event.event_date.day.alias('day')).where(
(Event.event_date.year == now.year) &
(Event.event_date.month == now.month))
注意
SQLiteにはネイティブな日付型がないため、日付はフォーマットされたテキスト列に格納されます。比較が正しく機能することを保証するために、日付は辞書式順序でソートされるようにフォーマットする必要があります。そのため、デフォルトではYYYY-MM-DD HH:MM:SS
として保存されます。
BitFieldとBigBitField
BitField
とBigBitField
は、3.0.0で新しく導入されました。前者は、整数のビットマスクとして機能トグルを格納するのに適したIntegerField
のサブクラスを提供します。後者は、大きなデータセットのビットマップを格納するのに適しています。たとえば、メンバーシップやビットマップ型のデータを表現します。
BitField
の使用例として、Postモデルがあり、投稿に関する特定のTrue/Falseフラグを保存したいとします。これらの機能トグルをすべて独自のBooleanField
オブジェクトに格納するか、代わりにBitField
を使用できます。
class Post(Model):
content = TextField()
flags = BitField()
is_favorite = flags.flag(1)
is_sticky = flags.flag(2)
is_minimized = flags.flag(4)
is_deleted = flags.flag(8)
これらのフラグの使用は非常に簡単です。
>>> p = Post()
>>> p.is_sticky = True
>>> p.is_minimized = True
>>> print(p.flags) # Prints 4 | 2 --> "6"
6
>>> p.is_favorite
False
>>> p.is_sticky
True
また、Postクラスのフラグを使用して、クエリで式を構築することもできます。
# Generates a WHERE clause that looks like:
# WHERE (post.flags & 1 != 0)
favorites = Post.select().where(Post.is_favorite)
# Query for sticky + favorite posts:
sticky_faves = Post.select().where(Post.is_sticky & Post.is_favorite)
BitField
は整数に格納されるため、表現できるフラグは最大64個です(64ビットは整数列の一般的なサイズです)。任意に大きなビットマップを格納するには、代わりにBigBitField
を使用できます。これは、BlobField
に格納された、自動的に管理されるバイトバッファを使用します。
BitField
内の1つ以上のビットを一括更新する場合、ビット演算子を使用して1つ以上のビットを設定またはクリアできます。
# Set the 4th bit on all Post objects.
Post.update(flags=Post.flags | 8).execute()
# Clear the 1st and 3rd bits on all Post objects.
Post.update(flags=Post.flags & ~(1 | 4)).execute()
単純な操作では、フラグは個々のビットを設定またはクリアするための便利なset()
メソッドとclear()
メソッドを提供します。
# Set the "is_deleted" bit on all posts.
Post.update(flags=Post.is_deleted.set()).execute()
# Clear the "is_deleted" bit on all posts.
Post.update(flags=Post.is_deleted.clear()).execute()
使用例
class Bitmap(Model):
data = BigBitField()
bitmap = Bitmap()
# Sets the ith bit, e.g. the 1st bit, the 11th bit, the 63rd, etc.
bits_to_set = (1, 11, 63, 31, 55, 48, 100, 99)
for bit_idx in bits_to_set:
bitmap.data.set_bit(bit_idx)
# We can test whether a bit is set using "is_set":
assert bitmap.data.is_set(11)
assert not bitmap.data.is_set(12)
# We can clear a bit:
bitmap.data.clear_bit(11)
assert not bitmap.data.is_set(11)
# We can also "toggle" a bit. Recall that the 63rd bit was set earlier.
assert bitmap.data.toggle_bit(63) is False
assert bitmap.data.toggle_bit(63) is True
assert bitmap.data.is_set(63)
# BigBitField supports item accessor by bit-number, e.g.:
assert bitmap.data[63]
bitmap.data[0] = 1
del bitmap.data[0]
# We can also combine bitmaps using bitwise operators, e.g.
b = Bitmap(data=b'\x01')
b.data |= b'\x02'
assert list(b.data) == [1, 1, 0, 0, 0, 0, 0, 0]
assert len(b.data) == 1
BareField
BareField
クラスは、SQLiteでのみ使用することを目的としています。SQLiteは動的な型付けを使用し、データ型が強制されないため、データ型なしでフィールドを宣言してもまったく問題ありません。そのような場合は、BareField
を使用できます。また、SQLite仮想テーブルでメタ列または型なし列を使用することも一般的であるため、そのような場合も型なしフィールドを使用できます(ただし、全文検索の場合は、代わりにSearchField
を使用する必要があります!)。
BareField
は、特別なパラメータ adapt
を受け入れます。このパラメータは、データベースから取得した値を適切な Python の型に変換する関数です。たとえば、型なしの列を持つ仮想テーブルがあり、それが int
オブジェクトを返すことがわかっている場合は、adapt=int
を指定できます。
例
db = SqliteDatabase(':memory:')
class Junk(Model):
anything = BareField()
class Meta:
database = db
# Store multiple data-types in the Junk.anything column:
Junk.create(anything='a string')
Junk.create(anything=12345)
Junk.create(anything=3.14159)
カスタムフィールドの作成
peewee にカスタムフィールド型のサポートを追加するのは簡単です。この例では、postgresql 用の UUID フィールド (ネイティブの UUID 列型を持つ) を作成します。
カスタムフィールド型を追加するには、まず、フィールドデータが格納される列の型を特定する必要があります。たとえば、10進数フィールドの上に Python の動作を追加するだけの場合 (たとえば、通貨フィールドを作成する場合) は、DecimalField
をサブクラス化するだけです。一方、データベースがカスタム列型を提供している場合は、peewee に知らせる必要があります。これは、Field.field_type
属性によって制御されます。
注意
Peewee には UUIDField
が付属していますが、次のコードはあくまで例として意図されています。
まず、UUID フィールドを定義することから始めましょう
class UUIDField(Field):
field_type = 'uuid'
UUID をネイティブの UUID 列に格納します。psycopg2 はデフォルトでデータを文字列として扱うため、フィールドに次の 2 つのメソッドを追加して処理します。
アプリケーションで使用するためにデータベースから出力されるデータ
Python アプリからデータベースに入るデータ
import uuid
class UUIDField(Field):
field_type = 'uuid'
def db_value(self, value):
return value.hex # convert UUID to hex string.
def python_value(self, value):
return uuid.UUID(value) # convert hex string to UUID
このステップはオプションです。 デフォルトでは、field_type
の値は、データベーススキーマの列のデータ型に使用されます。フィールドデータに異なるデータ型を使用する複数のデータベースをサポートする必要がある場合は、この *uuid* ラベルをデータベース内の実際の *uuid* 列型にマップする方法をデータベースに知らせる必要があります。Database
コンストラクタでオーバーライドを指定します。
# Postgres, we use UUID data-type. db = PostgresqlDatabase('my_db', field_types={'uuid': 'uuid'}) # Sqlite doesn't have a UUID type, so we use text type. db = SqliteDatabase('my_db', field_types={'uuid': 'text'})
以上です! 一部のフィールドは、postgresql HStore フィールドがキー/値ストアのように動作し、*contains* や *update* などの操作のためのカスタム演算子を持つなど、特殊な操作をサポートする場合があります。カスタム演算 も指定できます。コード例については、playhouse.postgres_ext
の HStoreField
のソースコードを確認してください。
フィールド名の競合
Model
クラスは、Model.save()
や Model.create()
など、多くのクラスおよびインスタンスメソッドを実装しています。名前がモデルメソッドと一致するフィールドを宣言すると、問題が発生する可能性があります。次を検討してください。
class LogEntry(Model):
event = TextField()
create = TimestampField() # Uh-oh.
update = TimestampField() # Uh-oh.
データベーススキーマで目的の列名を使用しながらこの問題を回避するには、フィールド属性の代替名を指定しながら、column_name
を明示的に指定します。
class LogEntry(Model):
event = TextField()
create_ = TimestampField(column_name='create')
update_ = TimestampField(column_name='update')
モデルテーブルの作成
モデルの使用を開始するには、データベースへの接続を開き、最初にテーブルを作成する必要があります。Peewee は必要な *CREATE TABLE* クエリを実行し、さらに制約とインデックスを作成します。
# Connect to our database.
db.connect()
# Create the tables.
db.create_tables([User, Tweet])
注意
厳密に言えば、connect()
を呼び出す必要はありませんが、明示的に行うことをお勧めします。そうすれば、何か問題が発生した場合、エラーは後で任意のタイミングで発生するのではなく、接続ステップで発生します。
注意
デフォルトでは、Peewee はテーブルを作成するときに IF NOT EXISTS
句を含めます。これを無効にする場合は、safe=False
を指定します。
テーブルを作成した後、データベーススキーマを変更する場合 (列を追加、削除、または変更する場合) は、次のいずれかを行う必要があります。
テーブルを削除して再作成する。
1つ以上の *ALTER TABLE* クエリを実行します。Peewee には、これを大幅に簡略化できるスキーマ移行ツールが付属しています。詳細については、スキーマ移行 のドキュメントを確認してください。
モデルオプションとテーブルメタデータ
モデルの名前空間を汚染しないように、モデル固有の構成は *Meta* という特別なクラスに配置されます (django フレームワークから借用した規則)。
from peewee import *
contacts_db = SqliteDatabase('contacts.db')
class Person(Model):
name = CharField()
class Meta:
database = contacts_db
これにより、*Person* でクエリが実行されるたびに、Peewee は contacts データベースを使用するように指示されます。
注意
サンプルモデル を確認してください。データベースを定義し、拡張した BaseModel
を作成したことに気付くでしょう。これは、データベースを定義し、モデルを作成する推奨される方法です。
クラスが定義されたら、ModelClass.Meta
にアクセスするのではなく、代わりに ModelClass._meta
を使用する必要があります。
>>> Person.Meta
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Person' has no attribute 'Meta'
>>> Person._meta
<peewee.ModelOptions object at 0x7f51a2f03790>
ModelOptions
クラスは、モデルメタデータ (フィールドのリスト、外部キー関係など) を取得するのに役立つ可能性のあるいくつかのメソッドを実装しています。
>>> Person._meta.fields
{'id': <peewee.AutoField object at 0x7f51a2e92750>,
'name': <peewee.CharField object at 0x7f51a2f0a510>}
>>> Person._meta.primary_key
<peewee.AutoField object at 0x7f51a2e92750>
>>> Person._meta.database
<peewee.SqliteDatabase object at 0x7f519bff6dd0>
Meta
属性として指定できるオプションがいくつかあります。ほとんどのオプションは継承可能ですが、一部はテーブル固有であり、サブクラスによって継承されません。
オプション |
意味 |
継承可能か? |
---|---|---|
|
モデルのデータベース |
はい |
|
データを格納するテーブルの名前 |
いいえ |
|
テーブル名を動的に生成する関数 |
はい |
|
インデックスを作成するフィールドのリスト |
はい |
|
|
はい |
|
テーブル制約のリスト |
はい |
|
モデルのデータベーススキーマ |
はい |
|
model.save() を呼び出すとき、ダーティフィールドのみを保存 |
はい |
|
テーブル作成拡張機能のオプションの辞書 |
はい |
|
閉じ括弧の後に続く設定文字列のリスト |
はい |
|
一時テーブルを示す |
はい |
|
レガシーテーブル名生成を使用 (デフォルトで有効) |
はい |
|
このテーブルの作成が別のテーブルに依存していることを示す |
いいえ |
|
テーブルに rowid を含めないことを示す (SQLite のみ) |
いいえ |
|
厳密なデータ型を示す (SQLite のみ、3.37+) |
はい |
継承可能な属性と継承不可能な属性を示す例を次に示します。
>>> db = SqliteDatabase(':memory:')
>>> class ModelOne(Model):
... class Meta:
... database = db
... table_name = 'model_one_tbl'
...
>>> class ModelTwo(ModelOne):
... pass
...
>>> ModelOne._meta.database is ModelTwo._meta.database
True
>>> ModelOne._meta.table_name == ModelTwo._meta.table_name
False
Meta.primary_key
Meta.primary_key
属性は、CompositeKey
を指定するか、モデルに主キーが*ない*ことを示すために使用されます。複合主キーの詳細については、こちらをご覧ください: 複合主キー。
モデルに主キーを含めないことを示すには、primary_key = False
を設定します。
例
class BlogToTag(Model):
"""A simple "through" table for many-to-many relationship."""
blog = ForeignKeyField(Blog)
tag = ForeignKeyField(Tag)
class Meta:
primary_key = CompositeKey('blog', 'tag')
class NoPrimaryKey(Model):
data = IntegerField()
class Meta:
primary_key = False
テーブル名
デフォルトでは、Peewee はモデルクラスの名前に基づいてテーブル名を自動的に生成します。テーブル名が生成される方法は、Meta.legacy_table_names
の値によって異なります。デフォルトでは、下位互換性を損なわないように、legacy_table_names=True
です。ただし、新しい改良されたテーブル名生成を使用する場合は、legacy_table_names=False
を指定できます。
この表は、legacy_table_names
の値に応じて、モデル名が SQL テーブル名に変換される方法の違いを示しています。
モデル名 |
legacy_table_names=True |
legacy_table_names=False (新規) |
---|---|---|
ユーザー |
user |
user |
UserProfile |
userprofile |
user_profile |
APIResponse |
apiresponse |
api_response |
WebHTTPRequest |
webhttprequest |
web_http_request |
mixedCamelCase |
mixedcamelcase |
mixed_camel_case |
Name2Numbers3XYZ |
name2numbers3xyz |
name2_numbers3_xyz |
注意
下位互換性を維持するために、現在のリリース (Peewee 3.x) では、デフォルトで legacy_table_names=True
が指定されています。
次のメジャーリリース (Peewee 4.0) では、legacy_table_names
のデフォルト値は False
になります。
モデルクラスのテーブル名を明示的に指定するには、table_name
Meta オプションを使用します。この機能は、ぎこちない命名規則を使用している可能性のある既存のデータベーススキーマを処理する場合に役立ちます。
class UserProfile(Model):
class Meta:
table_name = 'user_profile_tbl'
独自の命名規則を実装する場合は、table_function
Meta オプションを指定できます。この関数はモデルクラスで呼び出され、目的のテーブル名を文字列として返す必要があります。会社で、テーブル名が小文字で、"_tbl" で終わる必要があると指定していると仮定すると、これをテーブル関数として実装できます。
def make_table_name(model_class):
model_name = model_class.__name__
return model_name.lower() + '_tbl'
class BaseModel(Model):
class Meta:
table_function = make_table_name
class User(BaseModel):
# table_name will be "user_tbl".
class UserProfile(BaseModel):
# table_name will be "userprofile_tbl".
インデックスと制約
Peewee は、単一または複数の列にインデックスを作成でき、オプションで *UNIQUE* 制約を含めることができます。Peewee は、モデルとフィールドの両方でユーザー定義の制約もサポートしています。
単一列のインデックスと制約
単一列のインデックスは、フィールド初期化パラメータを使用して定義されます。次の例では、*username* フィールドに一意のインデックスを追加し、*email* フィールドに通常のインデックスを追加します。
class User(Model):
username = CharField(unique=True)
email = CharField(index=True)
列にユーザー定義の制約を追加するには、constraints
パラメータを使用して渡すことができます。スキーマの一部としてデフォルト値を指定したり、CHECK
制約を追加したりできます (例)。
class Product(Model):
name = CharField(unique=True)
price = DecimalField(constraints=[Check('price < 10000')])
created = DateTimeField(
constraints=[SQL("DEFAULT (datetime('now'))")])
複数列のインデックス
複数カラムのインデックスは、ネストされたタプルを使用してMeta属性として定義できます。各データベースインデックスは2要素タプルで、最初の要素はフィールド名のタプル、2番目の要素はインデックスを一意にするかどうかを示すブール値です。
class Transaction(Model):
from_acct = CharField()
to_acct = CharField()
amount = DecimalField()
date = DateTimeField()
class Meta:
indexes = (
# create a unique on from/to/date
(('from_acct', 'to_acct', 'date'), True),
# create a non-unique on from/to
(('from_acct', 'to_acct'), False),
)
注意
インデックスのタプルに項目が1つしかない場合は、末尾にカンマを追加することを忘れないでください。
class Meta:
indexes = (
(('first_name', 'last_name'), True), # Note the trailing comma!
)
高度なインデックス作成
Peeweeは、Model.add_index()
メソッドを使用するか、ModelIndex
ヘルパークラスを直接使用して、モデルにインデックスを宣言するためのより構造化されたAPIをサポートしています。
例
class Article(Model):
name = TextField()
timestamp = TimestampField()
status = IntegerField()
flags = IntegerField()
# Add an index on "name" and "timestamp" columns.
Article.add_index(Article.name, Article.timestamp)
# Add a partial index on name and timestamp where status = 1.
Article.add_index(Article.name, Article.timestamp,
where=(Article.status == 1))
# Create a unique index on timestamp desc, status & 4.
idx = Article.index(
Article.timestamp.desc(),
Article.flags.bin_and(4),
unique=True)
Article.add_index(idx)
警告
SQLiteは、パラメータ化されたCREATE INDEX
クエリをサポートしていません。つまり、SQLiteを使用して式またはスカラ値を含むインデックスを作成する場合は、SQL
ヘルパーを使用してインデックスを宣言する必要があります。
# SQLite does not support parameterized CREATE INDEX queries, so
# we declare it manually.
Article.add_index(SQL('CREATE INDEX ...'))
詳細については、add_index()
を参照してください。
詳細については、以下を参照してください。
テーブル制約
Peeweeでは、Model
に任意の制約を追加できます。これは、スキーマが作成されるときにテーブル定義の一部になります。
たとえば、人の名前と姓の2つの列の複合主キーを持つpeopleテーブルがあるとします。peopleテーブルに関連する別のテーブルが必要で、これを行うには外部キー制約を定義する必要があります。
class Person(Model):
first = CharField()
last = CharField()
class Meta:
primary_key = CompositeKey('first', 'last')
class Pet(Model):
owner_first = CharField()
owner_last = CharField()
pet_name = CharField()
class Meta:
constraints = [SQL('FOREIGN KEY(owner_first, owner_last) '
'REFERENCES person(first, last)')]
テーブルレベルでCHECK
制約を実装することもできます。
class Product(Model):
name = CharField(unique=True)
price = DecimalField()
class Meta:
constraints = [Check('price < 10000')]
主キー、複合キー、その他のトリック
AutoField
は、自動インクリメント整数主キーを識別するために使用されます。主キーを指定しない場合、Peeweeは「id」という名前の自動インクリメント主キーを自動的に作成します。
別のフィールド名を使用して自動インクリメントIDを指定するには、次のように記述できます。
class Event(Model):
event_id = AutoField() # Event.event_id will be auto-incrementing PK.
name = CharField()
timestamp = DateTimeField(default=datetime.datetime.now)
metadata = BlobField()
別のフィールドを主キーとして識別できます。その場合、「id」列は作成されません。この例では、人のメールアドレスを主キーとして使用します。
class Person(Model):
email = CharField(primary_key=True)
name = TextField()
dob = DateField()
警告
自動インクリメント整数主キーを期待して、次のように記述する人をよく見かけます。
class MyModel(Model):
id = IntegerField(primary_key=True)
Peeweeは上記のモデル宣言を、整数主キーを持つモデルとして理解しますが、そのIDの値はアプリケーションによって決定されます。自動インクリメント整数主キーを作成するには、代わりに次のように記述します。
class MyModel(Model):
id = AutoField() # primary_key=True is implied.
複合主キーは、CompositeKey
を使用して宣言できます。これを行うと、ForeignKeyField
で問題が発生する可能性があることに注意してください。Peeweeは「複合外部キー」の概念をサポートしていないためです。したがって、複合主キーは、単純な多対多の中間テーブルなどのごくわずかな状況でのみ使用することをお勧めします。
class Image(Model):
filename = TextField()
mimetype = CharField()
class Tag(Model):
label = CharField()
class ImageTag(Model): # Many-to-many relationship.
image = ForeignKeyField(Image)
tag = ForeignKeyField(Tag)
class Meta:
primary_key = CompositeKey('image', 'tag')
主キーなしでモデルを宣言したい非常にまれなケースでは、モデルのMeta
オプションでprimary_key = False
を指定できます。
整数以外の主キー
整数以外の主キーを使用したい場合(これは一般的にはお勧めしません)、フィールドを作成するときにprimary_key=True
を指定できます。自動インクリメントしない主キーを使用してモデルの新しいインスタンスを作成したい場合は、save()
でforce_insert=True
を指定する必要があります。
from peewee import *
class UUIDModel(Model):
id = UUIDField(primary_key=True)
自動インクリメントIDは、その名前が示すように、データベースに新しい行を挿入するときに自動的に生成されます。save()
を呼び出すと、peeweeは主キー値の存在に基づいてINSERTを実行するか、UPDATEを実行するかを決定します。UUIDの例では、データベースドライバーが新しいIDを生成しないため、手動で指定する必要があります。最初にsave()を呼び出すときは、force_insert = True
を渡します。
# This works because .create() will specify `force_insert=True`.
obj1 = UUIDModel.create(id=uuid.uuid4())
# This will not work, however. Peewee will attempt to do an update:
obj2 = UUIDModel(id=uuid.uuid4())
obj2.save() # WRONG
obj2.save(force_insert=True) # CORRECT
# Once the object has been created, you can call save() normally.
obj2.save()
注意
整数以外の主キーを持つモデルへの外部キーは、関連付けられている主キーと同じ基になるストレージタイプを使用するForeignKeyField
を持ちます。
複合主キー
Peeweeは、複合キーに対して非常に基本的なサポートを提供しています。複合キーを使用するには、モデルオプションのprimary_key
属性をCompositeKey
インスタンスに設定する必要があります。
class BlogToTag(Model):
"""A simple "through" table for many-to-many relationship."""
blog = ForeignKeyField(Blog)
tag = ForeignKeyField(Tag)
class Meta:
primary_key = CompositeKey('blog', 'tag')
警告
Peeweeは、CompositeKey
主キーを定義するモデルへの外部キーをサポートしていません。複合主キーを持つモデルに外部キーを追加する場合は、関連モデルの列を複製し、カスタムアクセサー(プロパティなど)を追加します。
手動で主キーを指定する
たとえば、リレーショナルデータを一括ロードする場合など、データベースに主キーの値を自動的に生成させたくない場合があります。これを1回限りで処理するには、インポート中にpeeweeにauto_increment
をオフにするように指示するだけです。
data = load_user_csv() # load up a bunch of data
User._meta.auto_increment = False # turn off auto incrementing IDs
with db.atomic():
for row in data:
u = User(id=row[0], username=row[1])
u.save(force_insert=True) # <-- force peewee to insert row
User._meta.auto_increment = True
上記のことを、ハックに頼らずに実現するより良い方法は、Model.insert_many()
APIを使用することです。
data = load_user_csv()
fields = [User.id, User.username]
with db.atomic():
User.insert_many(data, fields=fields).execute()
主キーを常に制御したい場合は、AutoField
フィールドタイプを使用せず、通常のIntegerField
(またはその他の列タイプ)を使用してください。
class User(BaseModel):
id = IntegerField(primary_key=True)
username = CharField()
>>> u = User.create(id=999, username='somebody')
>>> u.id
999
>>> User.get(User.username == 'somebody').id
999
主キーのないモデル
主キーのないモデルを作成したい場合は、内部のMeta
クラスでprimary_key = False
を指定できます。
class MyData(BaseModel):
timestamp = DateTimeField()
value = IntegerField()
class Meta:
primary_key = False
これにより、次のDDLが生成されます。
CREATE TABLE "mydata" (
"timestamp" DATETIME NOT NULL,
"value" INTEGER NOT NULL
)
警告
一部のモデルAPIは、主キーのないモデルでは正しく機能しない場合があります。たとえば、save()
やdelete_instance()
などです(代わりに、insert()
、update()
、delete()
を使用できます)。
自己参照外部キー
階層構造を作成する場合、子オブジェクトをその親にリンクする自己参照外部キーを作成する必要があります。自己参照外部キーをインスタンス化する時点ではモデルクラスが定義されていないため、特別な文字列'self'
を使用して自己参照外部キーを示す必要があります。
class Category(Model):
name = CharField()
parent = ForeignKeyField('self', null=True, backref='children')
ご覧のとおり、外部キーは親オブジェクトを上方にポイントし、後方参照はchildrenという名前になっています。
注意
自己参照外部キーは常にnull=True
である必要があります。
自己参照外部キーを含むモデルに対してクエリを実行する場合、自己結合を実行する必要がある場合があります。その場合は、Model.alias()
を使用してテーブル参照を作成できます。次に、自己結合を使用してカテゴリと親モデルをクエリする方法を示します。
Parent = Category.alias()
GrandParent = Category.alias()
query = (Category
.select(Category, Parent)
.join(Parent, on=(Category.parent == Parent.id))
.join(GrandParent, on=(Parent.parent == GrandParent.id))
.where(GrandParent.name == 'some category')
.order_by(Category.name))
循環外部キーの依存関係
2つのテーブル間で循環依存関係を作成することがあります。
注意
私の個人的な意見では、循環外部キーはコードの臭いであり、(たとえば、中間テーブルを追加することで)リファクタリングする必要があります。
Peeweeで循環外部キーを追加するのは少し難しいです。いずれかの外部キーを定義する時点では、それが指すモデルはまだ定義されていないため、NameError
が発生します。
class User(Model):
username = CharField()
favorite_tweet = ForeignKeyField(Tweet, null=True) # NameError!!
class Tweet(Model):
message = TextField()
user = ForeignKeyField(User, backref='tweets')
1つのオプションは、生のIDを格納するためにIntegerField
を使用することです。
class User(Model):
username = CharField()
favorite_tweet_id = IntegerField(null=True)
DeferredForeignKey
を使用することで、問題を回避し、外部キーフィールドを使用し続けることができます。
class User(Model):
username = CharField()
# Tweet has not been defined yet so use the deferred reference.
favorite_tweet = DeferredForeignKey('Tweet', null=True)
class Tweet(Model):
message = TextField()
user = ForeignKeyField(User, backref='tweets')
# Now that Tweet is defined, "favorite_tweet" has been converted into
# a ForeignKeyField.
print(User.favorite_tweet)
# <ForeignKeyField: "user"."favorite_tweet">
ただし、もう1つ注意すべき点があります。create_table
を呼び出すと、同じ問題が発生します。このため、peeweeは遅延外部キーの外部キー制約を自動的に作成しません。
テーブルと外部キー制約を作成するには、テーブルを作成した後、SchemaManager.create_foreign_key()
メソッドを使用して制約を作成できます。
# Will create the User and Tweet tables, but does *not* create a
# foreign-key constraint on User.favorite_tweet.
db.create_tables([User, Tweet])
# Create the foreign-key constraint:
User._schema.create_foreign_key(User.favorite_tweet)
注意
SQLiteはテーブルの変更に対するサポートが限られているため、作成後にテーブルに外部キー制約を追加することはできません。