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:

  1. class Event(Model):
  2. event_id = AutoField() # Event.event_id will be auto-incrementing PK.
  3. name = CharField()
  4. timestamp = DateTimeField(default=datetime.datetime.now)
  5. 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:

  1. class Person(Model):
  2. email = CharField(primary_key=True)
  3. name = TextField()
  4. dob = DateField()

Warning

I frequently see people write the following, expecting an auto-incrementinginteger primary key:

  1. class MyModel(Model):
  2. 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:

  1. class MyModel(Model):
  2. 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:

  1. class Image(Model):
  2. filename = TextField()
  3. mimetype = CharField()
  4.  
  5. class Tag(Model):
  6. label = CharField()
  7.  
  8. class ImageTag(Model): # Many-to-many relationship.
  9. image = ForeignKeyField(Image)
  10. tag = ForeignKeyField(Tag)
  11.  
  12. class Meta:
  13. 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.

  1. from peewee import *
  2.  
  3. class UUIDModel(Model):
  4. 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:

  1. # This works because .create() will specify `force_insert=True`.
  2. obj1 = UUIDModel.create(id=uuid.uuid4())
  3.  
  4. # This will not work, however. Peewee will attempt to do an update:
  5. obj2 = UUIDModel(id=uuid.uuid4())
  6. obj2.save() # WRONG
  7.  
  8. obj2.save(force_insert=True) # CORRECT
  9.  
  10. # Once the object has been created, you can call save() normally.
  11. 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:

  1. class BlogToTag(Model):
  2. """A simple "through" table for many-to-many relationship."""
  3. blog = ForeignKeyField(Blog)
  4. tag = ForeignKeyField(Tag)
  5.  
  6. class Meta:
  7. 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_incrementduring the import:

  1. data = load_user_csv() # load up a bunch of data
  2.  
  3. User._meta.auto_increment = False # turn off auto incrementing IDs
  4. with db.atomic():
  5. for row in data:
  6. u = User(id=row[0], username=row[1])
  7. u.save(force_insert=True) # <-- force peewee to insert row
  8.  
  9. User._meta.auto_increment = True

Although a better way to accomplish the above, without resorting to hacks, isto use the Model.insert_many() API:

  1. data = load_user_csv()
  2. fields = [User.id, User.username]
  3. with db.atomic():
  4. 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):

  1. class User(BaseModel):
  2. id = IntegerField(primary_key=True)
  3. username = CharField()
  4.  
  5. >>> u = User.create(id=999, username='somebody')
  6. >>> u.id
  7. 999
  8. >>> User.get(User.username == 'somebody').id
  9. 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:

  1. class MyData(BaseModel):
  2. timestamp = DateTimeField()
  3. value = IntegerField()
  4.  
  5. class Meta:
  6. primary_key = False

This will yield the following DDL:

  1. CREATE TABLE "mydata" (
  2. "timestamp" DATETIME NOT NULL,
  3. "value" INTEGER NOT NULL
  4. )

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()).