Non-integer Primary Keys, Composite Keys and other Tricks

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.

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

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

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

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

  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 use the PrimaryKeyField field type, but use a normal IntegerField (or other column type):

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

  1. class MyData(BaseModel):
  2. timestamp = DateTimeField()
  3. value = IntegerField()
  4. class Meta:
  5. 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() and delete()).