Primary Keys, Composite Keys and other Tricks
The AutoField
is used to identify an auto-incrementing integerprimary key. If you do not specify a primary key, Peewee will automaticallycreate 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 emailaddress 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-incrementinginteger primary key:
- class MyModel(Model):
- id = IntegerField(primary_key=True)
Peewee understands the above model declaration as a model with an integerprimary key, but the value of that ID is determined by the application. Tocreate 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
. Notethat doing this may cause issues with ForeignKeyField
, as Peeweedoes not support the concept of a “composite foreign-key”. As such, I’ve foundit 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’trecommend), you can specify primary_key=True
when creating a field. Whenyou wish to create a new instance for a model using a non-autoincrementingprimary key, you need to be sure you save()
specifyingforce_insert=True
.
- from peewee import *
- class UUIDModel(Model):
- id = UUIDField(primary_key=True)
Auto-incrementing IDs are, as their name says, automatically generated for youwhen you insert a new row into the database. When you callsave()
, peewee determines whether to do an INSERT versus anUPDATE based on the presence of a primary key value. Since, with our uuidexample, the database driver won’t generate a new ID, we need to specify itmanually. 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 aForeignKeyField
use the same underlying storage type as the primary keythey are related to.
Composite primary keys
Peewee has very basic support for composite keys. In order to use a compositekey, you must set the primary_key
attribute of the model options to aCompositeKey
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 aCompositeKey
primary key. If you wish to add a foreign-key to amodel that has a composite primary key, replicate the columns on therelated 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 forthe primary key, for instance when bulk loading relational data. To handle thison 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, isto 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 usethe AutoField
field type, but use a normalIntegerField
(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 specifyprimary_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()
anddelete()
).