モデルとフィールド

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)
  1. Databaseのインスタンスを作成します。

    db = SqliteDatabase('my_app.db')
    

    dbオブジェクトは、Sqliteデータベースへの接続を管理するために使用されます。この例では、SqliteDatabaseを使用していますが、他のデータベースエンジンのいずれかを使用することもできます。

  2. データベースを指定する基本モデルクラスを作成します。

    class BaseModel(Model):
        class Meta:
            database = db
    

    データベース接続を確立する基本モデルクラスを定義することをお勧めします。これにより、後続のモデルでデータベースを指定する必要がなくなり、コードがDRYになります。

    モデル構成は、Metaという特別なクラスに名前空間で保持されます。この規約はDjangoから借用しています。Meta構成はサブクラスに渡されるため、プロジェクトのモデルはすべてBaseModelをサブクラス化します。Model.Metaを使用して構成できる多くの異なる属性があります。

  3. モデルクラスを定義します。

    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

AutoField

integer

serial

integer

BigAutoField

integer

bigserial

bigint

IntegerField

integer

integer

integer

BigIntegerField

integer

bigint

bigint

SmallIntegerField

integer

smallint

smallint

IdentityField

サポートされていません

int identity

サポートされていません

FloatField

real

real

real

DoubleField

real

double precision

double precision

DecimalField

decimal

numeric

numeric

CharField

varchar

varchar

varchar

FixedCharField

char

char

char

TextField

text

text

text

BlobField

blob

bytea

blob

BitField

integer

bigint

bigint

BigBitField

blob

bytea

blob

UUIDField

text

uuid

varchar(40)

BinaryUUIDField

blob

bytea

varbinary(16)

DateTimeField

datetime

timestamp

datetime

DateField

date

date

date

TimeField

time

time

time

TimestampField

integer

integer

integer

IPField

integer

bigint

bigint

BooleanField

integer

boolean

bool

BareField

型なし

サポートされていません

サポートされていません

ForeignKeyField

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 – オプションのイテラブル。 valuedisplayの2タプルが含まれます

  • help_text = None – このフィールドの役立つテキストを表す文字列

  • verbose_name = None – このフィールドの「ユーザーフレンドリーな」名前を表す文字列

  • index_type = None – カスタムインデックスタイプを指定します。たとえば、Postgresの場合は、'BRIN'または'GIN'インデックスを指定できます。

いくつかのフィールドは特別なパラメータを取ります…

フィールド型

特別なパラメータ

CharField

max_length

FixedCharField

max_length

DateTimeField

フォーマット

DateField

フォーマット

TimeField

フォーマット

TimestampField

resolution, utc

DecimalField

max_digits, decimal_places, auto_round, rounding

ForeignKeyField

model, field, backref, on_delete, on_update, deferrable, lazy_load

BareField

適応

注意

defaultchoicesの両方は、それぞれDEFAULTCHECK 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)

注意

ミュータブルな型(listdictなど)を受け入れるフィールドを使用し、デフォルトを提供したい場合は、複数のモデルインスタンスが同じ基になるオブジェクトへの参照を共有しないように、デフォルト値を単純な関数でラップすることをお勧めします。

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

BitFieldBigBitFieldは、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_extHStoreField のソースコードを確認してください。

フィールド名の競合

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 属性として指定できるオプションがいくつかあります。ほとんどのオプションは継承可能ですが、一部はテーブル固有であり、サブクラスによって継承されません。

オプション

意味

継承可能か?

データベース

モデルのデータベース

はい

table_name

データを格納するテーブルの名前

いいえ

table_function

テーブル名を動的に生成する関数

はい

インデックス

インデックスを作成するフィールドのリスト

はい

primary_key

CompositeKey インスタンス

はい

制約

テーブル制約のリスト

はい

スキーマ

モデルのデータベーススキーマ

はい

only_save_dirty

model.save() を呼び出すとき、ダーティフィールドのみを保存

はい

オプション

テーブル作成拡張機能のオプションの辞書

はい

table_settings

閉じ括弧の後に続く設定文字列のリスト

はい

一時的

一時テーブルを示す

はい

legacy_table_names

レガシーテーブル名生成を使用 (デフォルトで有効)

はい

depends_on

このテーブルの作成が別のテーブルに依存していることを示す

いいえ

without_rowid

テーブルに rowid を含めないことを示す (SQLite のみ)

いいえ

strict_tables

厳密なデータ型を示す (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はテーブルの変更に対するサポートが限られているため、作成後にテーブルに外部キー制約を追加することはできません。