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