Primary Keys, Composite Keys and other Tricks
The AutoField
is used to identify an auto-incrementing integer primary key. If you do not specify a primary key, Peewee will automatically create an auto-incrementing primary key named “id”.
To specify an auto-incrementing ID using a different field name, you can write:
class Event(Model):
event_id = AutoField() # Event.event_id will be auto-incrementing PK.
name = CharField()
timestamp = DateTimeField(default=datetime.datetime.now)
metadata = BlobField()
You can identify a different field as the primary key, in which case an “id” column will not be created. In this example we will use a person’s email address as the primary key:
class Person(Model):
email = CharField(primary_key=True)
name = TextField()
dob = DateField()
Warning
I frequently see people write the following, expecting an auto-incrementing integer primary key:
class MyModel(Model):
id = IntegerField(primary_key=True)
Peewee understands the above model declaration as a model with an integer primary key, but the value of that ID is determined by the application. To create an auto-incrementing integer primary key, you would instead write:
class MyModel(Model):
id = AutoField() # primary_key=True is implied.
Composite primary keys can be declared using CompositeKey
. Note that doing this may cause issues with ForeignKeyField
, as Peewee does not support the concept of a “composite foreign-key”. As such, I’ve found it only advisable to use composite primary keys in a handful of situations, such as trivial many-to-many junction tables:
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')
In the extremely rare case you wish to declare a model with no primary key, you can specify primary_key = False
in the model Meta
options.
Non-integer primary keys
If you would like use a non-integer primary key (which I generally don’t recommend), you can specify primary_key=True
when creating a field. When you wish to create a new instance for a model using a non-autoincrementing primary key, you need to be sure you save()
specifying force_insert=True
.
from peewee import *
class UUIDModel(Model):
id = UUIDField(primary_key=True)
Auto-incrementing IDs are, as their name says, automatically generated for you when you insert a new row into the database. When you call save()
, peewee determines whether to do an INSERT versus an UPDATE based on the presence of a primary key value. Since, with our uuid example, the database driver won’t generate a new ID, we need to specify it manually. When we call save() for the first time, pass in 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()
Note
Any foreign keys to a model with a non-integer primary key will have a ForeignKeyField
use the same underlying storage type as the primary key they are related to.
Composite primary keys
Peewee has very basic support for composite keys. In order to use a composite key, you must set the primary_key
attribute of the model options to a CompositeKey
instance:
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')
Warning
Peewee does not support foreign-keys to models that define a CompositeKey
primary key. If you wish to add a foreign-key to a model that has a composite primary key, replicate the columns on the related model and add a custom accessor (e.g. a property).
Manually specifying primary keys
Sometimes you do not want the database to automatically generate a value for the primary key, for instance when bulk loading relational data. To handle this on a one-off basis, you can simply tell peewee to turn off auto_increment
during the import:
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
Although a better way to accomplish the above, without resorting to hacks, is to use the Model.insert_many()
API:
data = load_user_csv()
fields = [User.id, User.username]
with db.atomic():
User.insert_many(data, fields=fields).execute()
If you always want to have control over the primary key, simply do not use the AutoField
field type, but use a normal IntegerField
(or other column type):
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
Models without a Primary Key
If you wish to create a model with no primary key, you can specify primary_key = False
in the inner Meta
class:
class MyData(BaseModel):
timestamp = DateTimeField()
value = IntegerField()
class Meta:
primary_key = False
This will yield the following DDL:
CREATE TABLE "mydata" (
"timestamp" DATETIME NOT NULL,
"value" INTEGER NOT NULL
)
Warning
Some model APIs may not work correctly for models without a primary key, for instance save()
and delete_instance()
(you can instead use insert()
, update()
and delete()
).