Models and Fields
A Peewee model is a class whose attributes are field declarations - name = TextField() - and Peewee turns that class into a table and each instance into a row. The field type is the column type, the field's arguments are the column constraints (null, default, unique, max length), and the Meta inner class carries the table-level options (the database, the table name, the ordering). This chapter is the reference for the field types and the mapping rules, with the blog domain (authors, articles) as the running example, and it ends with the model introspection that shows you exactly what table Peewee built.
The Field Types
from peewee import *
class Author(Model):
# the field type is the column type; args are the constraints
name = CharField(max_length=120)
email = CharField(unique=True)
bio = TextField(null=True) # nullable
created_at = DateTimeField(default=datetime.now)
is_active = BooleanField(default=True)
class Meta:
database = db
# table_name defaults to the lowercased class name + 's'
# ordering by a field is a Meta option
ordering = ["-created_at"] # newest first
class Article(Model):
title = CharField(max_length=200)
slug = CharField(unique=True)
body = TextField()
author = ForeignKeyField(Author, related_name="articles")
published = BooleanField(default=False)
views = IntegerField(default=0)
rating = FloatField(null=True)
class Meta:
database = db
indexes = (
# a composite index: (author, published)
(("author", "published"), False),
)
The field classes map to the SQL types: CharField (a string, with max_length), TextField (long text), IntegerField, FloatField, BooleanField, DateTimeField/DateField/TimeField, and the binary and JSON families (BlobField, JSONField). The constraint arguments are the column options: null=True makes the column nullable (the default is not-null), default=... sets a default value, unique=True adds the unique constraint, and primary_key marks the key (the implicit id is the default primary key). The ForeignKeyField (the relationships chapter) references another model, and the Meta.indexes tuple declares composite indexes. The mapping is direct and inspectable - the next section shows the generated table.
The Meta Class and Table Options
class Article(Model):
title = CharField()
body = TextField()
class Meta:
database = db
table_name = "posts" # override the default name
ordering = ["-id"] # default query ordering
indexes = ((("title",), True),) # a unique index on title
# constraints, db_table options, and the database selection
# all live here - the table-level, not column-level, config
The Meta inner class is where the table-level configuration lives, separate from the column-level field arguments. database selects the connection (required when there is more than one), table_name overrides the default (the lowercased class name, pluralized), ordering sets the default order for queries on the model, and indexes declares the indexes Peewee creates with the table. Keeping the column options on the fields and the table options in Meta is the Peewee convention, and it is what makes a model readable - the columns are the class body, the table is the Meta.
Introspection: What Table Did Peewee Build
# the SQL Peewee generates for the model:
print(Author.create_table().__class__) # a query object
# the actual DDL, as Peewee will run it:
print(Author._meta.database.get_table_sql("authors"))
# the columns, as Peewee sees them:
for field in Author._meta.fields:
print(field.name, field.column_type)
# the table name and the database:
print(Author._meta.table_name) # 'authors'
print(Author._meta.database) # the SqliteDatabase instance
The _meta attribute is the model's mapping object, and it is the introspection surface: get_table_sql returns the CREATE TABLE Peewee will issue, _meta.fields is the ordered field list with each field's column type, and _meta.table_name is the table. Reading the generated DDL is the habit that keeps you in control of the schema - you see exactly what columns and constraints the model produces, which is the difference between an ORM you trust and one you guess at. The safe=True argument to create_table (create only if not exists) is the development convenience, and the migrations chapter is the production path for changing the schema after creation.
Model Conventions
- CapWords model names (
Author,Article), which become the table names (lowercased, pluralized) unlessMeta.table_nameoverrides. - One model per table, and the fields are the columns - a field you do not need as a column should not be on the model (computed values are properties or query-time, not fields).
- Names the data uses -
created_at, notcreated;is_active, notactive- because the column names are what the SQL and the database show, and clear names pay off in every query and every migration. - Nullable is a decision -
null=Trueis explicit, because the default (not-null) is the safer column, and a null column you did not mean is a data-quality hole. - The primary key is the implicit
idunless you override it with aprimary_key=Truefield - the default integer id is the right choice for most tables.
Summary
A model is a class of field declarations - the field type is the column type, the arguments are the constraints (null, default, unique, max_length), and the ForeignKeyField references another model. The Meta inner class carries the table-level options - the database selection, the table name, the default ordering, the indexes - separate from the column-level field arguments. The field vocabulary covers the standard SQL types (char, text, integer, float, boolean, the date/time family, blob, JSON), and the _meta attribute is the introspection surface - get_table_sql shows the exact CREATE TABLE, so you can verify the schema the model produces. Follow the conventions - CapWords names, one model per table, explicit nullability, clear column names, the implicit integer id - and the models are a readable, inspectable description of the schema that the rest of the tutorial queries.