Fields

The Field class is used to describe the mapping of Model attributes to database columns. Each field type has a corresponding SQL storage class (i.e. varchar, int), and conversion between python data types and underlying storage is handled transparently.

When creating a Model class, fields are defined as class attributes. This should look familiar to users of the django framework. Here’s an example:

  1. class User(Model):
  2. username = CharField()
  3. join_date = DateTimeField()
  4. about_me = TextField()

In the above example, because none of the fields are initialized with primary_key=True, an auto-incrementing primary key will automatically be created and named “id”.

There is one special type of field, ForeignKeyField, which allows you to represent foreign-key relationships between models in an intuitive way:

  1. class Message(Model):
  2. user = ForeignKeyField(User, backref='messages')
  3. body = TextField()
  4. send_date = DateTimeField(default=datetime.datetime.now)

This allows you to write code like the following:

  1. >>> print(some_message.user.username)
  2. Some User
  3. >>> for message in some_user.messages:
  4. ... print(message.body)
  5. some message
  6. another message
  7. yet another message

Note

Refer to the Relationships and Joins document for an in-depth discussion of foreign-keys, joins and relationships between models.

For full documentation on fields, see the Fields API notes

Field types table

Field TypeSqlitePostgresqlMySQL
IntegerFieldintegerintegerinteger
BigIntegerFieldintegerbigintbigint
SmallIntegerFieldintegersmallintsmallint
AutoFieldintegerserialinteger
BigAutoFieldintegerbigserialbigint
IdentityFieldnot supportedint identitynot supported
FloatFieldrealrealreal
DoubleFieldrealdouble precisiondouble precision
DecimalFielddecimalnumericnumeric
CharFieldvarcharvarcharvarchar
FixedCharFieldcharcharchar
TextFieldtexttextlongtext
BlobFieldblobbyteablob
BitFieldintegerbigintbigint
BigBitFieldblobbyteablob
UUIDFieldtextuuidvarchar(40)
BinaryUUIDFieldblobbyteavarbinary(16)
DateTimeFielddatetimetimestampdatetime
DateFielddatedatedate
TimeFieldtimetimetime
TimestampFieldintegerintegerinteger
IPFieldintegerbigintbigint
BooleanFieldintegerbooleanbool
BareFielduntypednot supportednot supported
ForeignKeyFieldintegerintegerinteger

Note

Don’t see the field you’re looking for in the above table? It’s easy to create custom field types and use them with your models.

Field initialization arguments

Parameters accepted by all field types and their default values:

  • null = False – allow null values
  • index = False – create an index on this column
  • unique = 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 models
  • primary_key = False – primary key for the table
  • constraints = 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 / index
  • unindexed = False – indicate field on virtual table should be unindexed (SQLite-only)
  • choices = None – optional iterable containing 2-tuples of value, display
  • help_text = None – string representing any helpful text for this field
  • verbose_name = None – string representing the “user-friendly” name of this field

Some fields take special parameters…

Field typeSpecial Parameters
CharFieldmax_length
FixedCharFieldmax_length
DateTimeFieldformats
DateFieldformats
TimeFieldformats
TimestampFieldresolution, utc
DecimalFieldmax_digits, decimal_places, auto_round, rounding
ForeignKeyFieldmodel, field, backref, on_delete, on_update, deferrable
BareFieldadapt

Note

Both default and choices could be implemented at the database level as DEFAULT and CHECK CONSTRAINT respectively, but any application change would require a schema change. Because of this, default is implemented purely in python and choices are not validated but exist for 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. For example to have an IntegerField default to zero rather than NULL, you could declare the field with a default value:

  1. class Message(Model):
  2. context = TextField()
  3. read_count = IntegerField(default=0)

In some instances it may make sense for the default value to be dynamic. A common scenario is using the current date and time. Peewee allows you to specify a function in these cases, whose return value will be used when the object is created. Note we only provide the function, we do not actually call it:

  1. class Message(Model):
  2. context = TextField()
  3. 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 default value in a simple function so that multiple model instances are not sharing a reference to the same underlying object:

  1. def house_defaults():
  2. return {'beds': 0, 'baths': 0}
  3. class House(Model):
  4. number = TextField()
  5. street = TextField()
  6. attributes = JSONField(default=house_defaults)

The database can also provide the default value for a field. While peewee does not explicitly provide an API for setting a server-side default value, you can use the constraints parameter to specify the server default:

  1. class Message(Model):
  2. context = TextField()
  3. timestamp = DateTimeField(constraints=[SQL('DEFAULT CURRENT_TIMESTAMP')])

Note

Remember: when using the default parameter, the values are set by Peewee rather than being a part of the actual table and column definition.

ForeignKeyField

ForeignKeyField is a special field type that allows one model to reference another. Typically a foreign key will contain the primary key of the model it relates to (but you can specify a particular column by specifying a field).

Foreign keys allow data to be normalized. In our example models, there is a foreign key from Tweet to User. This means that all the users are stored in their own table, as are the tweets, and the foreign key from tweet to user allows each tweet to point to a particular user object.

Note

Refer to the Relationships and Joins document for an in-depth discussion of foreign keys, joins and relationships between models.

In peewee, accessing the value of a ForeignKeyField will return the entire related object, e.g.:

  1. tweets = (Tweet
  2. .select(Tweet, User)
  3. .join(User)
  4. .order_by(Tweet.create_date.desc()))
  5. for tweet in tweets:
  6. print(tweet.user.username, tweet.message)

In the example above the User data was selected as part of the query. For more examples of this technique, see the Avoiding N+1 document.

If we did not select the User, though, then an additional query would be issued to fetch the associated User data:

  1. tweets = Tweet.select().order_by(Tweet.create_date.desc())
  2. for tweet in tweets:
  3. # WARNING: an additional query will be issued for EACH tweet
  4. # to fetch the associated User data.
  5. print(tweet.user.username, tweet.message)

Sometimes you only need the associated primary key value from the foreign key column. In this case, Peewee follows the convention established by Django, of allowing you to access the raw foreign key value by appending "_id" to the foreign key field’s name:

  1. tweets = Tweet.select()
  2. for tweet in tweets:
  3. # Instead of "tweet.user", we will just get the raw ID value stored
  4. # in the column.
  5. print(tweet.user_id, tweet.message)

ForeignKeyField allows for a backreferencing property to be bound to the target model. Implicitly, this property will be named classname_set, where classname is the lowercase name of the class, but can be overridden using the parameter backref:

  1. class Message(Model):
  2. from_user = ForeignKeyField(User, backref='outbox')
  3. to_user = ForeignKeyField(User, backref='inbox')
  4. text = TextField()
  5. for message in some_user.outbox:
  6. # We are iterating over all Messages whose from_user is some_user.
  7. print(message)
  8. for message in some_user.inbox:
  9. # We are iterating over all Messages whose to_user is some_user
  10. print(message)

DateTimeField, DateField and TimeField

The three fields devoted to working with dates and times have special properties which 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 have an events calendar and want to highlight all the days in the current month that have an event attached:

  1. # Get the current time.
  2. now = datetime.datetime.now()
  3. # Get days that have events for the current month.
  4. Event.select(Event.event_date.day.alias('day')).where(
  5. (Event.event_date.year == now.year) &
  6. (Event.event_date.month == now.month))

Note

SQLite does not have a native date type, so dates are stored in formatted text columns. To ensure that comparisons work correctly, the dates need to be formatted so they are sorted lexicographically. That is why they are stored, by default, as YYYY-MM-DD HH:MM:SS.

BitField and BigBitField

The BitField and BigBitField are new as of 3.0.0. The former provides a subclass of IntegerField that is suitable for storing feature toggles as an integer bitmask. The latter is suitable for storing a bitmap for a large data-set, e.g. expressing membership or bitmap-type data.

As an example of using BitField, let’s say we have a Post model and we wish to store certain True/False flags about how the post. We could store all these feature toggles in their own BooleanField objects, or we could use BitField instead:

  1. class Post(Model):
  2. content = TextField()
  3. flags = BitField()
  4. is_favorite = flags.flag(1)
  5. is_sticky = flags.flag(2)
  6. is_minimized = flags.flag(4)
  7. is_deleted = flags.flag(8)

Using these flags is quite simple:

  1. >>> p = Post()
  2. >>> p.is_sticky = True
  3. >>> p.is_minimized = True
  4. >>> print(p.flags) # Prints 4 | 2 --> "6"
  5. 6
  6. >>> p.is_favorite
  7. False
  8. >>> p.is_sticky
  9. True

We can also use the flags on the Post class to build expressions in queries:

  1. # Generates a WHERE clause that looks like:
  2. # WHERE (post.flags & 1 != 0)
  3. favorites = Post.select().where(Post.is_favorite)
  4. # Query for sticky + favorite posts:
  5. sticky_faves = Post.select().where(Post.is_sticky & Post.is_favorite)

Since the BitField is stored in an integer, there is a maximum of 64 flags you can represent (64-bits is common size of integer column). For storing arbitrarily large bitmaps, you can instead use BigBitField, which uses an automatically managed buffer of bytes, stored in a BlobField.

Example usage:

  1. class Bitmap(Model):
  2. data = BigBitField()
  3. bitmap = Bitmap()
  4. # Sets the ith bit, e.g. the 1st bit, the 11th bit, the 63rd, etc.
  5. bits_to_set = (1, 11, 63, 31, 55, 48, 100, 99)
  6. for bit_idx in bits_to_set:
  7. bitmap.data.set_bit(bit_idx)
  8. # We can test whether a bit is set using "is_set":
  9. assert bitmap.data.is_set(11)
  10. assert not bitmap.data.is_set(12)
  11. # We can clear a bit:
  12. bitmap.data.clear_bit(11)
  13. assert not bitmap.data.is_set(11)
  14. # We can also "toggle" a bit. Recall that the 63rd bit was set earlier.
  15. assert bitmap.data.toggle_bit(63) is False
  16. assert bitmap.data.toggle_bit(63) is True
  17. assert bitmap.data.is_set(63)

BareField

The BareField class is intended to be used only with SQLite. Since SQLite uses dynamic typing and data-types are not enforced, it can be perfectly fine to declare fields without any data-type. In those cases you can use BareField. It is also common for SQLite virtual tables to use meta-columns or untyped columns, so for those cases as well you may wish to use an untyped field (although for full-text search, you should use SearchField instead!).

BareField accepts a special parameter adapt. This parameter is a function that takes a value coming from the database and converts it into the appropriate Python type. For instance, if you have a virtual table with an un-typed column but you know that it will return int objects, you can specify adapt=int.

Example:

  1. db = SqliteDatabase(':memory:')
  2. class Junk(Model):
  3. anything = BareField()
  4. class Meta:
  5. database = db
  6. # Store multiple data-types in the Junk.anything column:
  7. Junk.create(anything='a string')
  8. Junk.create(anything=12345)
  9. Junk.create(anything=3.14159)

Creating a custom field

It is easy to add support for custom field types in peewee. In this example we will 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 the field 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 just subclass DecimalField. On the other hand, if the database offers a custom column type you will need to let peewee know. This is controlled by the Field.field_type attribute.

Note

Peewee ships with a UUIDField, the following code is intended only as an example.

Let’s start by defining our UUID field:

  1. class UUIDField(Field):
  2. field_type = 'uuid'

We will store the UUIDs in a native UUID column. Since psycopg2 treats the data as 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
  1. import uuid
  2. class UUIDField(Field):
  3. field_type = 'uuid'
  4. def db_value(self, value):
  5. return value.hex # convert UUID to hex string.
  6. def python_value(self, value):
  7. return uuid.UUID(value) # convert hex string to UUID

This step is optional. By default, the field_type value will be used for the columns data-type in the database schema. If you need to support multiple databases which use different data-types for your field-data, we need to 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:

  1. # Postgres, we use UUID data-type.
  2. db = PostgresqlDatabase('my_db', field_types={'uuid': 'uuid'})
  3. # Sqlite doesn't have a UUID type, so we use text type.
  4. db = SqliteDatabase('my_db', field_types={'uuid': 'text'})

That is it! Some fields may support exotic operations, like the postgresql HStore field acts like a key/value store and has custom operators for things like contains and update. You can specify custom operations as well. For example code, check out the source code for the 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 a field whose name coincides with a model method, it could cause problems. Consider:

  1. class LogEntry(Model):
  2. event = TextField()
  3. create = TimestampField() # Uh-oh.
  4. update = TimestampField() # Uh-oh.

To avoid this problem while still using the desired column name in the database schema, explicitly specify the column_name while providing an alternative name for the field attribute:

  1. class LogEntry(Model):
  2. event = TextField()
  3. create_ = TimestampField(column_name='create')
  4. update_ = TimestampField(column_name='update')