Fields
The Field
class is used to describe the mapping ofModel
attributes to database columns. Each field type has acorresponding SQL storage class (i.e. varchar, int), and conversion betweenpython data types and underlying storage is handled transparently.
When creating a Model
class, fields are defined as classattributes. This should look familiar to users of the django framework. Here’san example:
- class User(Model):
- username = CharField()
- join_date = DateTimeField()
- about_me = TextField()
In the above example, because none of the fields are initialized withprimary_key=True
, an auto-incrementing primary key will automatically becreated and named “id”. Peewee uses AutoField
to signify anauto-incrementing integer primary key, which implies primary_key=True
.
There is one special type of field, ForeignKeyField
, which allowsyou to represent foreign-key relationships between models in an intuitive way:
- class Message(Model):
- user = ForeignKeyField(User, backref='messages')
- body = TextField()
- send_date = DateTimeField(default=datetime.datetime.now)
This allows you to write code like the following:
- >>> print(some_message.user.username)
- Some User
- >>> for message in some_user.messages:
- ... print(message.body)
- some message
- another message
- yet another message
Note
Refer to the Relationships and Joins document for an in-depth discussion offoreign-keys, joins and relationships between models.
For full documentation on fields, see the Fields API notes
Field types table
Field Type | Sqlite | Postgresql | MySQL |
---|---|---|---|
AutoField | integer | serial | integer |
BigAutoField | integer | bigserial | bigint |
IntegerField | integer | integer | integer |
BigIntegerField | integer | bigint | bigint |
SmallIntegerField | integer | smallint | smallint |
IdentityField | not supported | int identity | not supported |
FloatField | real | real | real |
DoubleField | real | double precision | double precision |
DecimalField | decimal | numeric | numeric |
CharField | varchar | varchar | varchar |
FixedCharField | char | char | char |
TextField | text | text | text |
BlobField | blob | bytea | blob |
BitField | integer | bigint | bigint |
BigBitField | blob | bytea | blob |
UUIDField | text | uuid | varchar(40) |
BinaryUUIDField | blob | bytea | varbinary(16) |
DateTimeField | datetime | timestamp | datetime |
DateField | date | date | date |
TimeField | time | time | time |
TimestampField | integer | integer | integer |
IPField | integer | bigint | bigint |
BooleanField | integer | boolean | bool |
BareField | untyped | not supported | not supported |
ForeignKeyField | integer | integer | integer |
Note
Don’t see the field you’re looking for in the above table? It’s easy tocreate custom field types and use them with your models.
- Creating a custom field
Database
, particularly thefields
parameter.
Field initialization arguments
Parameters accepted by all field types and their default values:
null = False
– allow null valuesindex = False
– create an index on this columnunique = False
– create a unique index on this column. See also adding composite indexes.column_name = None
– explicitly specify the column name in the database.default = None
– any value or callable to use as a default for uninitialized modelsprimary_key = False
– primary key for the tableconstraints = None
- one or more constraints, e.g.[Check('price > 0')]
sequence = None
– sequence name (if backend supports it)collation = None
– collation to use for ordering the field / indexunindexed = False
– indicate field on virtual table should be unindexed (SQLite-only)choices = None
– optional iterable containing 2-tuples ofvalue
,display
help_text = None
– string representing any helpful text for this fieldverbose_name = None
– string representing the “user-friendly” name of this fieldindex_type = None
– specify a custom index-type, e.g. for Postgres you might specify a'BRIN'
or'GIN'
index.
Some fields take special parameters…
Field type | Special Parameters |
---|---|
CharField | max_length |
FixedCharField | max_length |
DateTimeField | formats |
DateField | formats |
TimeField | formats |
TimestampField | resolution , utc |
DecimalField | max_digits , decimal_places ,auto_round , rounding |
ForeignKeyField | model , field , backref ,on_delete , on_update , deferrable lazy_load |
BareField | adapt |
Note
Both default
and choices
could be implemented at the database levelas DEFAULT and CHECK CONSTRAINT respectively, but any applicationchange would require a schema change. Because of this, default
isimplemented purely in python and choices
are not validated but existfor metadata purposes only.
To add database (server-side) constraints, use the constraints
parameter.
Default field values
Peewee can provide default values for fields when objects are created. Forexample to have an IntegerField
default to zero rather than NULL
, youcould declare the field with a default value:
- class Message(Model):
- context = TextField()
- read_count = IntegerField(default=0)
In some instances it may make sense for the default value to be dynamic. Acommon scenario is using the current date and time. Peewee allows you tospecify a function in these cases, whose return value will be used when theobject is created. Note we only provide the function, we do not actually _call_it:
- class Message(Model):
- context = TextField()
- timestamp = DateTimeField(default=datetime.datetime.now)
Note
If you are using a field that accepts a mutable type (list, dict, etc),and would like to provide a default, it is a good idea to wrap your defaultvalue in a simple function so that multiple model instances are not sharinga reference to the same underlying object:
- def house_defaults():
- return {'beds': 0, 'baths': 0}
- class House(Model):
- number = TextField()
- street = TextField()
- attributes = JSONField(default=house_defaults)
The database can also provide the default value for a field. While peewee doesnot explicitly provide an API for setting a server-side default value, you canuse the constraints
parameter to specify the server default:
- class Message(Model):
- context = TextField()
- timestamp = DateTimeField(constraints=[SQL('DEFAULT CURRENT_TIMESTAMP')])
Note
Remember: when using the default
parameter, the values are set byPeewee rather than being a part of the actual table and column definition.
ForeignKeyField
ForeignKeyField
is a special field type that allows one model toreference another. Typically a foreign key will contain the primary key of themodel it relates to (but you can specify a particular column by specifying afield
).
Foreign keys allow data to be normalized.In our example models, there is a foreign key from Tweet
to User
. Thismeans that all the users are stored in their own table, as are the tweets, andthe foreign key from tweet to user allows each tweet to point to a particularuser object.
Note
Refer to the Relationships and Joins document for an in-depth discussion offoreign keys, joins and relationships between models.
In peewee, accessing the value of a ForeignKeyField
will return theentire related object, e.g.:
- tweets = (Tweet
- .select(Tweet, User)
- .join(User)
- .order_by(Tweet.created_date.desc()))
- for tweet in tweets:
- print(tweet.user.username, tweet.message)
Note
In the example above the User
data was selected as part of the query.For more examples of this technique, see the Avoiding N+1document.
If we did not select the User
, though, then an additional query wouldbe issued to fetch the associated User
data:
- tweets = Tweet.select().order_by(Tweet.created_date.desc())
- for tweet in tweets:
- # WARNING: an additional query will be issued for EACH tweet
- # to fetch the associated User data.
- print(tweet.user.username, tweet.message)
Sometimes you only need the associated primary key value from the foreign keycolumn. In this case, Peewee follows the convention established by Django, ofallowing you to access the raw foreign key value by appending "_id"
to theforeign key field’s name:
- tweets = Tweet.select()
- for tweet in tweets:
- # Instead of "tweet.user", we will just get the raw ID value stored
- # in the column.
- print(tweet.user_id, tweet.message)
To prevent accidentally resolving a foreign-key and triggering an additionalquery, ForeignKeyField
supports an initialization paramaterlazy_load
which, when disabled, behaves like the "_id"
attribute. Forexample:
- class Tweet(Model):
- # ... same fields, except we declare the user FK to have
- # lazy-load disabled:
- user = ForeignKeyField(User, backref='tweets', lazy_load=False)
- for tweet in Tweet.select():
- print(tweet.user, tweet.message)
- # With lazy-load disabled, accessing tweet.user will not perform an extra
- # query and the user ID value is returned instead.
- # e.g.:
- # 1 tweet from user1
- # 1 another from user1
- # 2 tweet from user2
- # However, if we eagerly load the related user object, then the user
- # foreign key will behave like usual:
- for tweet in Tweet.select(Tweet, User).join(User):
- print(tweet.user.username, tweet.message)
- # user1 tweet from user1
- # user1 another from user1
- # user2 tweet from user1
ForeignKeyField Back-references
ForeignKeyField
allows for a backreferencing property to be boundto the target model. Implicitly, this property will be named classname_set
,where classname
is the lowercase name of the class, but can be overriddenusing the parameter backref
:
- class Message(Model):
- from_user = ForeignKeyField(User, backref='outbox')
- to_user = ForeignKeyField(User, backref='inbox')
- text = TextField()
- for message in some_user.outbox:
- # We are iterating over all Messages whose from_user is some_user.
- print(message)
- for message in some_user.inbox:
- # We are iterating over all Messages whose to_user is some_user
- print(message)
DateTimeField, DateField and TimeField
The three fields devoted to working with dates and times have special propertieswhich allow access to things like the year, month, hour, etc.
DateField
has properties for:
year
month
day
TimeField
has properties for:
hour
minute
second
DateTimeField
has all of the above.
These properties can be used just like any other expression. Let’s say we havean events calendar and want to highlight all the days in the current month thathave an event attached:
- # Get the current time.
- now = datetime.datetime.now()
- # Get days that have events for the current month.
- Event.select(Event.event_date.day.alias('day')).where(
- (Event.event_date.year == now.year) &
- (Event.event_date.month == now.month))
Note
SQLite does not have a native date type, so dates are stored in formattedtext columns. To ensure that comparisons work correctly, the dates need tobe formatted so they are sorted lexicographically. That is why they arestored, by default, as YYYY-MM-DD HH:MM:SS
.
BitField and BigBitField
The BitField
and BigBitField
are new as of 3.0.0. Theformer provides a subclass of IntegerField
that is suitable forstoring feature toggles as an integer bitmask. The latter is suitable forstoring a bitmap for a large data-set, e.g. expressing membership orbitmap-type data.
As an example of using BitField
, let’s say we have a Post modeland we wish to store certain True/False flags about how the post. We couldstore all these feature toggles in their own BooleanField
objects,or we could use BitField
instead:
- class Post(Model):
- content = TextField()
- flags = BitField()
- is_favorite = flags.flag(1)
- is_sticky = flags.flag(2)
- is_minimized = flags.flag(4)
- is_deleted = flags.flag(8)
Using these flags is quite simple:
- >>> p = Post()
- >>> p.is_sticky = True
- >>> p.is_minimized = True
- >>> print(p.flags) # Prints 4 | 2 --> "6"
- 6
- >>> p.is_favorite
- False
- >>> p.is_sticky
- True
We can also use the flags on the Post class to build expressions in queries:
- # Generates a WHERE clause that looks like:
- # WHERE (post.flags & 1 != 0)
- favorites = Post.select().where(Post.is_favorite)
- # Query for sticky + favorite posts:
- sticky_faves = Post.select().where(Post.is_sticky & Post.is_favorite)
Since the BitField
is stored in an integer, there is a maximum of64 flags you can represent (64-bits is common size of integer column). Forstoring arbitrarily large bitmaps, you can instead use BigBitField
,which uses an automatically managed buffer of bytes, stored in aBlobField
.
Example usage:
- class Bitmap(Model):
- data = BigBitField()
- bitmap = Bitmap()
- # Sets the ith bit, e.g. the 1st bit, the 11th bit, the 63rd, etc.
- bits_to_set = (1, 11, 63, 31, 55, 48, 100, 99)
- for bit_idx in bits_to_set:
- bitmap.data.set_bit(bit_idx)
- # We can test whether a bit is set using "is_set":
- assert bitmap.data.is_set(11)
- assert not bitmap.data.is_set(12)
- # We can clear a bit:
- bitmap.data.clear_bit(11)
- assert not bitmap.data.is_set(11)
- # We can also "toggle" a bit. Recall that the 63rd bit was set earlier.
- assert bitmap.data.toggle_bit(63) is False
- assert bitmap.data.toggle_bit(63) is True
- assert bitmap.data.is_set(63)
BareField
The BareField
class is intended to be used only with SQLite. SinceSQLite uses dynamic typing and data-types are not enforced, it can be perfectlyfine to declare fields without any data-type. In those cases you can useBareField
. It is also common for SQLite virtual tables to usemeta-columns or untyped columns, so for those cases as well you may wish to usean untyped field (although for full-text search, you should useSearchField
instead!).
BareField
accepts a special parameter adapt
. This parameter isa function that takes a value coming from the database and converts it into theappropriate Python type. For instance, if you have a virtual table with anun-typed column but you know that it will return int
objects, you canspecify adapt=int
.
Example:
- db = SqliteDatabase(':memory:')
- class Junk(Model):
- anything = BareField()
- class Meta:
- database = db
- # Store multiple data-types in the Junk.anything column:
- Junk.create(anything='a string')
- Junk.create(anything=12345)
- Junk.create(anything=3.14159)
Creating a custom field
It is easy to add support for custom field types in peewee. In this example wewill create a UUID field for postgresql (which has a native UUID column type).
To add a custom field type you need to first identify what type of column thefield data will be stored in. If you just want to add python behavior atop,say, a decimal field (for instance to make a currency field) you would justsubclass DecimalField
. On the other hand, if the database offers acustom column type you will need to let peewee know. This is controlled by theField.field_type
attribute.
Note
Peewee ships with a UUIDField
, the following code is intendedonly as an example.
Let’s start by defining our UUID field:
- class UUIDField(Field):
- field_type = 'uuid'
We will store the UUIDs in a native UUID column. Since psycopg2 treats the dataas a string by default, we will add two methods to the field to handle:
- The data coming out of the database to be used in our application
- The data from our python app going into the database
- import uuid
- class UUIDField(Field):
- field_type = 'uuid'
- def db_value(self, value):
- return value.hex # convert UUID to hex string.
- def python_value(self, value):
- return uuid.UUID(value) # convert hex string to UUID
This step is optional. By default, the fieldtype
value will be usedfor the columns data-type in the database schema. If you need to supportmultiple databases which use different data-types for your field-data, we needto let the database know how to map this _uuid label to an actual _uuid_column type in the database. Specify the overrides in the Database
constructor:
- # Postgres, we use UUID data-type.db = PostgresqlDatabase('my_db', field_types={'uuid': 'uuid'})# Sqlite doesn't have a UUID type, so we use text type.db = SqliteDatabase('my_db', field_types={'uuid': 'text'})
That is it! Some fields may support exotic operations, like the postgresqlHStore field acts like a key/value store and has custom operators for thingslike contains and update. You can specify custom operations as well. For example code, check out the source code forthe HStoreField
, in playhouse.postgres_ext
.
Field-naming conflicts
Model
classes implement a number of class- and instance-methods,for example Model.save()
or Model.create()
. If you declare afield whose name coincides with a model method, it could cause problems.Consider:
- class LogEntry(Model):
- event = TextField()
- create = TimestampField() # Uh-oh.
- update = TimestampField() # Uh-oh.
To avoid this problem while still using the desired column name in the databaseschema, explicitly specify the column_name
while providing an alternativename for the field attribute:
- class LogEntry(Model):
- event = TextField()
- create_ = TimestampField(column_name='create')
- update_ = TimestampField(column_name='update')