Database Tooling¶
This section covers the playhouse modules for managing connections, database URLs, schema migrations, introspection, code generation, and testing.
On this page
Database URLs¶
The playhouse.db_url module lets you configure Peewee from a connection
string, which is common in twelve-factor applications where database
credentials live in environment variables.
import os
from playhouse.db_url import connect
db = connect(os.environ.get('DATABASE_URL', 'sqlite:///default.db'))
Pass additional keyword arguments in the query string:
db = connect('postgres://user:pass@host/db?max_connections=20')
URL format: scheme://user:password@host:port/dbname?option=value
Common schemes:
Scheme |
Database class |
|---|---|
|
|
|
|
|
|
|
Connection pool implementations:
Scheme |
Database class |
|---|---|
|
|
|
|
|
|
|
Alternate drivers:
Scheme |
Database class |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- connect(url, unquote_password=False, unquote_user=False, **connect_params)¶
- Parameters
url – the URL for the database, see examples.
unquote_password (bool) – unquote special characters in the password.
unquote_user (bool) – unquote special characters in the user.
connect_params – additional parameters to pass to the Database.
Parse
urland return an appropriateDatabaseinstance. A URL without a database name raisesValueError.Examples:
sqlite:///my_app.db- SQLite file in the current directory.sqlite:///:memory:- in-memory SQLite.sqlite:////absolute/path/to/app.db- absolute path SQLite.postgresql:///dbname- database running locally.postgresql://user:password@host:5432/dbnamemysql://user:password@host:3306/dbname
- parse(url, unquote_password=False, unquote_user=False)¶
- Parameters
url – the URL for the database, see
connect()above for examples.unquote_password (bool) – unquote special characters in the password.
unquote_user (bool) – unquote special characters in the user.
Parse a URL and return a dictionary with
database,host,port,user, andpasswordkeys plus any extra connect parameters from the query string.Useful if you need to construct a database class manually:
params = parse('postgres://user:pass@host:5432/mydb') db = MyCustomDatabase(**params)
- register_database(db_class, *names)¶
- Parameters
db_class – A subclass of
Database.names – A list of names to use as the scheme in the URL.
Register a custom database class under one or more URL scheme names so that
connect()can instantiate it:register_database(ClickHouseDatabase, 'clickhouse') db = connect('clickhouse:///my-db')
Connection Pooling¶
The playhouse.pool module contains a number of Database classes
that provide transparent connection pooling for peewee databases. The pool
works by overriding the methods on the Database class that open and
close connections to the backend, and works as a drop-in replacement.
In multi-threaded or web applications, each thread gets its own connection. The
pool maintains up to max_connections open connections at any time. The
application only needs to ensure that connections are closed when work is
done (typically at the end of an HTTP request), so the connection can be
returned to the pool. Closing a pooled connection returns it to the pool rather
than actually disconnecting.
from playhouse.pool import PooledPostgresqlDatabase
db = PooledPostgresqlDatabase(
'my_app',
user='postgres',
max_connections=32,
stale_timeout=300)
Tip
Pooled database implementations may be safely used as drop-in replacements for their non-pooled counterparts.
Commonly-used pool implementations:
Additional implementations:
playhouse.cysqlite_ext-PooledCySqliteDatabaseplayhouse.mysql_ext-PooledMariaDBConnectorDatabaseplayhouse.mysql_ext-PooledMySQLConnectorDatabaseplayhouse.postgres_ext-PooledPostgresqlExtDatabaseplayhouse.postgres_ext-PooledPsycopg3Databaseplayhouse.cockroachdb-PooledCockroachDatabase
Note
Applications using Peewee’s asyncio integration do not need to use a special pooled database. Async databases use a connection pool by default.
- class PooledDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)¶
Common mixin class used for specific backend implementations.
- Parameters
database (str) – The name of the database or database file.
max_connections (int) – Maximum number of concurrent connections. Pass
Nonefor no limit.stale_timeout (int) – Seconds after which an idle connection is considered stale and will be discarded next time it would be reused.
timeout (int) – Seconds to block when all connections are in use.
0blocks indefinitely,None(default) raises immediately.
Connections will not be closed exactly when they exceed their
stale_timeout. Instead, stale connections are only closed when a new connection is requested.If the pool is exhausted and no
timeoutis configured, aMaxConnectionsExceededis raised.- manual_close()¶
Close the current connection permanently without returning it to the pool. Use this when a connection has entered a bad state.
- close_idle()¶
Close all pooled connections that are not currently in use.
- close_stale(age=600)¶
- Parameters
age (int) – Age at which a connection should be considered stale.
- Returns
Number of connections closed.
Close in-use connections that have exceeded
ageseconds. Use with caution.
- close_all()¶
Close all connections including those currently in use. Use with caution.
- class PooledSqliteDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)¶
Pool implementation for SQLite databases. Extends
SqliteDatabase.
- class PooledPostgresqlDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)¶
Pool implementation for Postgresql databases. Extends
PostgresqlDatabase.
- class PooledMySQLDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)¶
Pool implementation for MySQL / MariaDB databases. Extends
MySQLDatabase.
Additional implementations exist for:
Schema Migrations¶
The playhouse.migrate module provides a lightweight API for making
incremental schema changes to an existing database without writing raw SQL.
The peewee migration philosophy is that tools relying on database introspection, versioning, and auto-detection are often brittle and complex. Migrations can be written as simple python scripts and executed from the command-line. The runner below adds bookkeeping and can be used for migration generation and execution.
Supported schema-altering operations:
Add, rename, or drop columns.
Make columns nullable or not nullable.
Change a column’s type or server-side default.
Rename or drop a table.
Add or drop indexes and constraints.
See also
from playhouse.migrate import SchemaMigrator, migrate
migrator = SchemaMigrator.from_database(db)
with db.atomic():
migrate(
migrator.add_column('tweet', 'is_published', BooleanField(default=True)),
migrator.add_column('user', 'email', CharField(null=True)),
migrator.drop_column('user', 'old_bio'),
)
Tip
Wrap migrations in SchemaMigrator.migration_context() to ensure
changes are not partially applied when transactional DDL is available
(SQLite and Postgres). It also temporarily disables the SQLite
foreign-keys pragma, so table rebuilds (required for some operations)
do not destroy rows via ON DELETE CASCADE.
Operations¶
Add columns:
# Non-null fields must supply a default value.
migrate(
migrator.add_column('comment', 'pub_date', DateTimeField(null=True)),
migrator.add_column('comment', 'body', TextField(default='')),
)
Add a foreign key (the column name must include the _id suffix that
Peewee appends by default):
user_fk = ForeignKeyField(User, field=User.id, null=True)
migrate(
migrator.add_column('tweet', 'user_id', user_fk),
)
Rename a column:
migrate(
migrator.rename_column('story', 'pub_date', 'publish_date'),
migrator.rename_column('story', 'mod_date', 'modified_date'),
)
Drop a column:
migrate(migrator.drop_column('story', 'old_field'))
Nullable / not nullable:
migrate(
migrator.drop_not_null('story', 'pub_date'), # Allow NULLs.
migrator.add_not_null('story', 'modified_date'), # Disallow NULLs.
)
Change type:
# Change a VARCHAR(...) to a TEXT field.
migrate(migrator.alter_column_type('person', 'email', TextField()))
Rename table:
migrate(migrator.rename_table('story', 'stories'))
Drop table:
migrate(migrator.drop_table('story', safe=True))
Add / drop indexes:
# Specify table, column(s), and unique/non-unique.
migrate(
# Create an index on the `pub_date` column.
migrator.add_index('story', ('pub_date',), False),
# Create a unique index on the category and title fields.
migrator.add_index('story', ('category_id', 'title'), True),
# Drop the pub-date + status index.
migrator.drop_index('story', 'story_pub_date_status'),
)
# Only index published stories (sqlite and postgres).
story = Table('story')
migrate(migrator.add_index('story', ('pub_date',), False,
where=(story.c.status == 'published')))
# Unique index treating NULLs as equal (postgres 15+).
migrate(migrator.add_index('story', ('external_id',), True,
nulls_distinct=False))
Add / drop constraints:
from peewee import Check
# Add a CHECK() constraint to enforce the price cannot be negative.
migrate(migrator.add_constraint(
'products',
'price_check',
Check('price >= 0')))
# Remove the price check constraint.
migrate(migrator.drop_constraint('products', 'price_check'))
# Add a UNIQUE constraint on the first and last names.
migrate(migrator.add_unique('person', 'first_name', 'last_name'))
Column defaults:
# Add a default value:
migrate(migrator.add_column_default('entry', 'status', 'draft'))
# Use a function expression (not supported in SQLite):
migrate(migrator.add_column_default('entry', 'created_at', fn.NOW()))
# SQLite-compatible function syntax:
migrate(migrator.add_column_default(
'entry',
'created_at',
"(datetime('now'))"))
# Remove a default:
migrate(migrator.drop_column_default('entry', 'status'))
Raw SQL:
# Interleave data fixes with schema changes in a single plan.
migrate(
migrator.add_column('entry', 'score', IntegerField(null=True)),
migrator.sql('UPDATE entry SET score = %s', (0,)),
migrator.add_not_null('entry', 'score'),
)
params are bound by the database driver, so the placeholder is the
driver’s own: %s for postgres and mysql, ? for sqlite.
Note
Postgres users may need to set the search-path when using a non-standard schema. This can be done as follows:
migrator = PostgresqlMigrator(db)
migrate(
migrator.set_search_path('my_schema'),
migrator.add_column('table', 'field', TextField(default='')),
)
Migration API¶
- migrate(*operations)¶
Execute one or more schema-altering operations.
Usage:
migrate( migrator.add_column('t', 'col', CharField(default='')), migrator.add_index('t', ('col',), False), )
- class SchemaMigrator(database)¶
- Parameters
database – a
Databaseinstance.
The
SchemaMigratoris responsible for generating schema-altering statements.- classmethod from_database(database)¶
- Parameters
database (Database) – database instance to generate migrations for.
- Returns
SchemaMigratorinstance appropriate to provided database.
Factory method that returns the appropriate
SchemaMigratorsubclass for the given database.
- migrate(*operations)¶
Execute one or more schema-altering operations. Equivalent to the module-level
migrate()helper.
- migration_context(atomic=True)¶
Context manager wrapping a migration run in the appropriate per-dialect safety: a transaction when the database supports transactional DDL (and
atomicis true), and on sqlite theforeign_keyspragma is additionally disabled for the duration, as table-rewrites would otherwise fireON DELETE CASCADE.
- add_column(table, column_name, field, allow_not_null=False)¶
- Parameters
Add a new column to the provided table. The
fieldprovided will be used to generate the appropriate column definition.If the field is not nullable it must specify a default value, unless
allow_not_nullis set.Note
For non-null columns, the following occurs:
column is added as allowing NULLs
UPDATEquery is executed to populate the default valuecolumn is changed to NOT NULL
With
allow_not_null=Truethe column is addedNOT NULLin a single statement and no default is required. Existing rows are the caller’s responsibility: postgres and sqlite refuse to add the column, while MySQL backfills its implicit default.
- drop_column(table, column_name, cascade=True)¶
- Parameters
table (str) – Name of the table to drop column from.
column_name (str) – Name of the column to drop.
cascade (bool) – Whether the column should be dropped with CASCADE.
- rename_column(table, old_name, new_name)¶
- Parameters
table (str) – Name of the table containing column to rename.
old_name (str) – Current name of the column.
new_name (str) – New name for the column.
- add_not_null(table, column)¶
- Parameters
table (str) – Name of table containing column.
column (str) – Name of the column to make not nullable.
- drop_not_null(table, column)¶
- Parameters
table (str) – Name of table containing column.
column (str) – Name of the column to make nullable.
- add_column_default(table, column, default)¶
- Parameters
table (str) – Name of table containing column.
column (str) – Name of the column to add default to.
default – New default value for column. See notes below.
Peewee attempts to properly quote the default if it appears to be a string literal. Otherwise the default will be treated literally. Postgres and MySQL support specifying the default as a peewee expression, e.g.
fn.NOW(), but Sqlite users will need to use"(datetime('now'))"instead.
- drop_column_default(table, column)¶
- Parameters
table (str) – Name of table containing column.
column (str) – Name of the column to remove default from.
- alter_column_type(table, column, field, cast=None)¶
- Parameters
Alter the data-type of a column. This method should be used with care, as using incompatible types may not be well-supported by your database.
- rename_table(old_name, new_name)¶
- Parameters
old_name (str) – Current name of the table.
new_name (str) – New name for the table.
- drop_table(table, safe=False, cascade=False, schema=None)¶
- Parameters
table (str) – Name of the table to drop.
safe (bool) – Use
IF EXISTS.cascade (bool) – Append
CASCADE(not supported by sqlite).schema (str) – Optional schema qualification.
- add_index(table, columns, unique=False, using=None, where=None, nulls_distinct=None)¶
- Parameters
table (str) – Name of table on which to create the index.
columns (list) – List of columns which should be indexed.
unique (bool) – Whether the new index should specify a unique constraint.
using (str) – Index type (where supported), e.g. GiST or GIN.
where – Expression for a partial index (sqlite and postgres).
nulls_distinct (bool) – For unique indexes, whether NULL values are treated as distinct (postgres 15+).
- drop_index(table, index_name)¶
- Parameters
table (str) – Name of the table containing the index to be dropped.
index_name (str) – Name of the index to be dropped.
- sql(sql, params=None)¶
Raw SQL as an operation, letting data fixes interleave with schema changes in a single
migrate()call.
- add_constraint(table, name, constraint)¶
- drop_constraint(table, name)¶
- Parameters
table (str) – Table to drop constraint from.
name (str) – Name of constraint to drop.
- add_unique(table, *column_names)¶
- Parameters
table (str) – Table to add constraint to.
column_names (str) – One or more columns for UNIQUE constraint.
- class PostgresqlMigrator(database)¶
- set_search_path(schema_name)¶
Set the Postgres search path for subsequent operations.
- class SqliteMigrator(database)¶
SQLite supports or emulates most schema-altering operations, but the code path is dependent on the library version.
SQLite 3.53.0 added
ALTER TABLEsupport for table constraints, soadd_constraintanddrop_constraintwork on 3.53 and newer. Older versions raiseNotImplementedError.add_uniqueis not supported on any version, as sqlite’sADD CONSTRAINTdoes not extend to UNIQUE constraints.
- class MySQLMigrator(database)¶
MySQL-specific subclass.
Migration Runner¶
The playhouse.migrations module is a migration runner built using the
migration tooling. The runner records which scripts have been
applied and runs pending migrations in order. The CLI is installed as
pwmigrate (or use python -m playhouse.migrations).
The following commands are available:
initial: create an initial migration containing a snapshot of the application models.create: create a bare skeleton migration file.generate: generate a migration based on changes detected between the database schema and model definitions.diff: display differences between the database schema and model definitions.up: apply any pending migrations.down: roll back one or more applied migrations.fake: record pending migrations as applied without running them.status: show which migrations have been applied and which are pending.
The following sections show basic usage for common scenarios.
New application¶
Given a models module:
# models.py, imported as `app.models` in examples.
from peewee import *
database = SqliteDatabase('app.db')
class User(database.Model):
username = CharField(unique=True)
email = CharField()
Store the project defaults in a .pwmigrate file in the project root
(see pwmigrate config file), so commands need no arguments:
# .pwmigrate
database = app.models.database
models = app.models
# directory = migrations/
# table = schema_migration
Then generate the initial migration. initial reads the models and creates a
migration suitable for running on an empty database:
$ pwmigrate initial
migrations/0001_initial.py
Equivalent to above, passing the database URL and models module explicitly:
$ pwmigrate sqlite:///app.db initial app.models
migrations/0001_initial.py
The generated file is a python script. The model being created is recorded here as a frozen copy so that future changes to the application code do not affect the migration.
# Generated from a schema diff on 2026-08-02 21:28.
from peewee import *
def up(migrator, db):
class User(Model):
username = CharField(unique=True)
email = CharField()
class Meta:
database = db
table_name = 'user'
db.create_tables([User])
def down(migrator, db):
migrator.migrate(migrator.drop_table('user'))
Apply it using up:
$ pwmigrate up
applied: 0001_initial
Applying a migration runs its script and records the name in a
schema_migration table in the database.
Existing database¶
To get started with an existing database schema, generate an initial
migration that captures the current state. Then fake the initial migration.
We fake it instead of actually running it, since the schema already exists.
The fake command will record the initial migration as having been run:
$ pwmigrate app.models.database initial app.models
migrations/0001_initial.py
$ pwmigrate app.models.database fake # Fake it against real database.
faked: 0001_initial
Now the migrations and schema are in agreement. Creating an initial migration
is always a good idea, as it allows you to re-create your entire schema on a
fresh database using pwmigrate.
We can apply the above initial migration to stand up a test SQLite database, for example:
$ pwmigrate sqlite:///testing.db up
applied: 0001_initial
Tip
If model class definitions do not exist yet, they can be generated with pwiz:
$ pwiz -e postgresql -u postgres my_db > app/models.py
Making changes¶
In this example we’ll add a field to our model class:
class User(database.Model):
username = CharField(unique=True)
email = CharField()
karma = IntegerField(default=0) # This field is new.
Then we can run two commands to verify the change was picked up and generate a migration:
diffshows any changes that were identified.generatewrites the new migration.
Both commands read the database and models module from the .pwmigrate
file set up earlier (see pwmigrate config file).
# Prints a list of differences between code and database schema.
$ pwmigrate diff
add column user.karma
# Generates a migration.
$ pwmigrate generate add_karma
migrations/0002_add_karma.py
The generated up() adds the column:
def up(migrator, db):
migrator.migrate(migrator.add_column('user', 'karma', IntegerField(default=0)))
def down(migrator, db):
migrator.migrate(migrator.drop_column('user', 'karma'))
Peewee’s migration will populate default= for basic value types (str,
int, float, bool), for decimal.Decimal values, and for the
common callables datetime.datetime.now and utcnow,
datetime.date.today, time.time, time.time_ns, uuid.uuid4,
dict and list, adding any needed import. An enum member renders
as its value. Anything else will be marked with a TODO comment at the top
of the migration script.
The differ matches columns by name, so a renamed field diffs as an add
plus a drop. When the data must survive, replace the generated pair with
a rename_column() operation.
Run the migration:
$ pwmigrate up
applied: 0002_add_karma
$ pwmigrate status
[x] 0001_initial 2026-08-04 09:49:33
[x] 0002_add_karma 2026-08-04 09:49:33
Deployments run pwmigrate <db> up, or call run(db) from the
application’s startup code. Schema Diff describes what
generation covers.
Migration files¶
Peewee migrations are python files with a numeric prefix, defining
up(migrator, db) and, optionally, down(migrator, db). The
migrator is a SchemaMigrator bound to the connected
database and db is the Database, so inline models can
subclass db.Model.
For anything the differ cannot write, such as a data migration, scaffold
the next numbered file with create and fill in the body:
$ pwmigrate create create_notes
migrations/0003_create_notes.py
# migrations/0003_create_notes.py
from peewee import *
def up(migrator, db):
class User(db.Model): # Stub for the FK target.
pass
class Note(db.Model): # Copy model definition to decouple from app code.
user = ForeignKeyField(User)
content = TextField()
db.create_tables([Note])
def down(migrator, db):
migrator.migrate(migrator.drop_table('note'))
Data migrations are plain python between (or instead of) schema operations.
Generated files have the same shape. Treat one as a starting point, not a finished migration. Anything the differ cannot express is flagged with a TODO comment:
# Generated from a schema diff on 2026-08-03 16:17.
from peewee import *
# TODO: user.email: dropped column cannot be restored by down()
def up(migrator, db):
class User(Model):
class Meta:
database = db
table_name = 'user'
class Note(Model):
user = ForeignKeyField(User)
content = TextField()
class Meta:
database = db
table_name = 'note'
db.create_tables([Note])
migrator.migrate(migrator.add_column('user', 'karma', IntegerField(default=0)))
migrator.migrate(migrator.add_index('tweet', ('user_id', 'flags')))
migrator.migrate(migrator.drop_column('user', 'email'))
def down(migrator, db):
migrator.migrate(migrator.drop_index('tweet', 'tweet_user_id_flags'))
migrator.migrate(migrator.drop_column('user', 'karma'))
migrator.migrate(migrator.drop_table('note'))
Command line¶
Example usage:
# Generate the first migration from the models, assuming an empty db:
pwmigrate app.settings.db initial app.models
# Fake the initial migration (if models pre-date peewee migrations).
pwmigrate app.settings.db fake
# Or, run the initial migration if starting from a clean db.
pwmigrate app.settings.db up
# List migrations and when each was applied:
pwmigrate app.settings.db status
# Write a skeleton migration, or generate it from the model diff:
pwmigrate app.settings.db create "add karma"
pwmigrate app.settings.db generate "add karma" app.models
# Apply pending migrations, all or up through a target:
pwmigrate app.settings.db up
pwmigrate app.settings.db up 0002_add_karma
# Revert the most recent migration, or back through a target:
pwmigrate app.settings.db down
pwmigrate app.settings.db down 0002_add_karma
# Print schema drift against the models:
pwmigrate app.settings.db diff app.models
# Record all pending migrations as applied without running them:
pwmigrate app.settings.db fake
The database is given as:
Models are given as a dotted module path. Every model defined in the
module is used (field-less base classes are skipped and reported).
Models imported into the module from elsewhere are ignored, so a package
that only re-exports its models needs the explicit-list form,
app.models:MODELS.
pwmigrate config file¶
Keep a .pwmigrate file at the project root, committed with the
application code. It supplies per-project defaults as key = value
lines:
database = app.settings.db
models = app.models
# directory = migrations/
# table = schema_migration
This allows us to run migration commands without explicitly specifying the paths each time:
$ pwmigrate diff
$ pwmigrate generate "add some fields"
$ pwmigrate status
$ pwmigrate up
Recognized keys:
database- database spec (dotted path, url, or sqlite filename)directory- migrations directorymodels- models module, read bydiff,initialandgeneratetable- history table name
The file is read from the working directory only. A different config
file is named with -c. Unrecognized keys warn on stderr. Arguments
passed on the command line override any settings in the config file.
Commands¶
Command |
Meaning |
|---|---|
|
List migrations and applied timestamps. |
|
Apply pending migrations in order, stopping after
|
|
Revert the most recent migration, or everything back
through |
|
Generate the first migration from the models, assuming an empty database. |
|
Write a skeleton migration file. |
|
Generate a migration from the schema diff. |
|
Record pending migrations as applied without running them,
stopping after |
|
Print schema differences against a models module. |
Command-line options:
Option |
Meaning |
Example |
|---|---|---|
|
Migrations directory (default |
|
|
History table name (default |
|
|
Echo SQL as it executes |
|
|
Config file supplying defaults |
|
status exits 0 when the database is current and 1 when migrations
are pending, so it can gate a deploy. Validation and database errors
exit 2.
Behavior:
Files apply in numeric order (the prefix is parsed as an integer, so
2_x.pyruns before10_y.py). Applied names are recorded in a history table (defaultschema_migration), exposed as a model atrunner.History. The history is a set, so migrations merged in from a branch are applied even when their numbers are not the highest.On Postgres and SQLite, each migration and its history row are wrapped in a single transaction, so a failure rolls back cleanly. MySQL DDL commits implicitly, so the history row is written only after every operation succeeds. Set
atomic = Falseat module level to opt a migration out of transaction wrapping (e.g. forCREATE INDEX CONCURRENTLY).On SQLite,
foreign_keyspragma is turned off for the duration of each migration and restored afterwards to preventON DELETE CASCADEbeing triggered during table rebuilds (required for some operations).A migration that does not define
down()cannot be reverted (there is no requirement to write one).
Python interface¶
All migration-runner operations are available programmatically:
from playhouse.migrations import Runner
runner = Runner(db, directory='migrations')
runner.create('add karma') # Write a skeleton file.
runner.status() # [Migration(idx, name, path, applied), ...]
runner.up() # Apply everything pending, in order.
runner.up('0004_x') # Apply pending up through 0004_x.
runner.down() # Revert the most recent applied migration.
runner.down('0004_x') # Revert back through 0004_x, inclusive.
runner.fake() # Record all as applied without running.
run(db) is shorthand for Runner(db, 'migrations').up().
Generate a migration from a diff:
from playhouse.migrations import Runner, template
from playhouse.schema_diff import diff_models
runner = Runner(db)
diff = diff_models(db, [User, Tweet, Note])
if diff:
runner.create('add karma', body=template(diff))
- class Runner(database, directory='migrations', table_name='schema_migration')¶
- up(target=None)¶
Apply all pending migrations in order, stopping after
targetif given. Returns the applied names.
- down(target=None)¶
Revert the most recent applied migration, or, given a target, every applied migration back through the target (newest first). Returns the reverted names.
- status()¶
Return
Migrationnamedtuples(idx, name, path, applied)merging migration files with history rows, in numeric order.appliedis None when pending,pathis None when the file is missing.
- fake(target=None)¶
Record pending migrations as applied without running them, stopping after
targetif given. Returns the recorded names.
- create(name, body=None)¶
Write a numbered migration file (a skeleton, unless
bodyis given) and return its path.
- History¶
The history-table model, for manual bookkeeping repair.
- run(database, directory='migrations', **kwargs)¶
Apply pending migrations. Shorthand for
Runner(database, directory).up().
- template(diff)¶
Render a
playhouse.schema_diff.SchemaDiffas a migration-file body. See Schema Diff.
Schema Diff¶
The playhouse.schema_diff module compares model definitions against
the live database schema and reports basic differences:
tables to create
columns added or removed
indexes added or removed
Columns are compared by name alone (no types, nullability or constraints), so a rename appears as an addition plus a removal. Partial and expression indexes are compared by name only. Tables in the database that no model covers are ignored. Virtual models (sqlite FTS, etc.) are skipped.
>>> from playhouse.schema_diff import diff_models
>>> diff = diff_models(db, [User, Tweet, Note])
>>> bool(diff)
True
>>> print(diff)
create table note
add column user.karma
drop column user.email
add index tweet (user_id, flags)
- diff_models(database, models)¶
Compare the live schema against the given models. Returns a
SchemaDiffwhich is falsy when everything matches.
- class SchemaDiff¶
A named tuple of five lists, each mapping directly onto a
SchemaMigratorcall:
- class IndexDiff¶
Named tuple
(table, name, columns, unique).columns is Nonemarks a partial or expression index, detected by name but not described. Plain additions carrycolumns/uniqueand no name (the name is chosen at creation). Removals always carry the name to drop.
Reflection¶
The playhouse.reflection module introspects an existing database and
generates Peewee model classes from its schema. It is used internally by
pwiz - Model Generator and DataSet.
from playhouse.reflection import generate_models
db = PostgresqlDatabase('my_app')
models = generate_models(db) # Returns {table_name: ModelClass}
# list(models.keys())
# ['account', 'customer', 'order', 'orderitem', 'product']
# Get a reference to a generated model.
Customer = models['customer']
# Or inject into the current namespace:
# globals().update(models)
# Query generated models:
for customer in Customer.select():
print(customer.name, customer.email)
- generate_models(database, schema=None, **options)¶
- Parameters
database (Database) – database instance to introspect.
schema (str) – optional schema to introspect.
options – arbitrary options, see
Introspector.generate_models()for details.
- Returns
a
dictmapping table names to model classes.
- print_model(model)¶
Print a human-readable summary of a model’s fields and indexes to stdout. Useful for interactive exploration:
>>> print_model(Tweet) tweet id AUTO PK user INT FK: User.id content TEXT timestamp DATETIME index(es) user_id timestamp
- print_table_sql(model)¶
Print the
CREATE TABLESQL for a model class (without indexes):>>> print_table_sql(Tweet) CREATE TABLE IF NOT EXISTS "tweet" ( "id" INTEGER NOT NULL PRIMARY KEY, "user_id" INTEGER NOT NULL, "content" TEXT NOT NULL, "timestamp" DATETIME NOT NULL, FOREIGN KEY ("user_id") REFERENCES "user" ("id") )
- class Introspector(metadata, schema=None)¶
Metadata can be extracted from a database by instantiating an
Introspector. Rather than instantiating this class directly, it is recommended to use the factory methodfrom_database().- classmethod from_database(database, schema=None)¶
- Parameters
database – a
Databaseinstance.schema (str) – an optional schema (supported by some databases).
Creates an
Introspectorinstance suitable for use with the given database.db = SqliteDatabase('my_app.db') introspector = Introspector.from_database(db) models = introspector.generate_models() # User and Tweet (assumed to exist in the database) are # peewee Model classes generated from the database schema. User = models['user'] Tweet = models['tweet']
- generate_models(skip_invalid=False, table_names=None, literal_column_names=False, bare_fields=False, include_views=False)¶
- Parameters
skip_invalid (bool) – Skip tables whose names are not valid Python identifiers.
table_names (list) – Only generate models for the given tables.
literal_column_names (bool) – Use the exact database column names as field names (rather than converting to Python naming conventions).
bare_fields (bool) – Do not attempt to detect field types, use
BareFieldfor all columns (SQLite only).include_views (bool) – Also generate models for views.
- Returns
A dictionary mapping table-names to model classes.
Introspect the database, reading in the tables, columns, and foreign key constraints, then generate a dictionary mapping each database table to a dynamically-generated
Modelclass.
pwiz - Model Generator¶
pwiz is a command-line tool that introspects a database and prints
ready-to-use Peewee model code. If you have an existing database, running
pwiz saves significant time generating the initial model definitions.
# Introspect a Postgresql database and write models to a file:
pwiz -e postgresql -u postgres my_db > models.py
# Introspect a SQLite database:
pwiz -e sqlite path/to/my.db
# Introspect a MySQL database (prompts for password):
pwiz -e mysql -u root -P my_db
# Introspect only specific tables:
pwiz -e postgresql my_db -t user,tweet,follow
Command-line options:
Option |
Meaning |
Example |
|---|---|---|
|
Database backend |
|
|
Host |
|
|
Port |
|
|
Username |
|
|
Password (prompts interactively) |
|
|
Schema |
|
|
Comma-separated list of tables to include |
|
|
Include views |
|
|
Embed database info as a comment |
|
|
Preserve original column order |
|
|
Ignore fields whose type is unknown |
|
|
Use legacy table and column naming |
Valid -e values: sqlite, mysql, postgresql, cockroachdb.
Warning
If a password is required to access your database, you will be prompted to enter it using a secure prompt.
The password will be included in the output. Specifically, at the top
of the file a Database will be defined along with any required
parameters - including the password.
Example output for a SQLite database with user and tweet tables:
from peewee import *
database = SqliteDatabase('example.db')
class UnknownField(object):
def __init__(self, *_, **__): pass
class BaseModel(Model):
class Meta:
database = database
class User(BaseModel):
username = TextField(unique=True)
class Meta:
table_name = 'user'
class Tweet(BaseModel):
content = TextField()
timestamp = DateTimeField()
user = ForeignKeyField(column_name='user_id', field='id', model=User)
class Meta:
table_name = 'tweet'
Note that pwiz detects foreign keys, unique constraints, and preserves
explicit table names.
Note
The UnknownField is a placeholder that is used in the event your schema
contains a column declaration that Peewee doesn’t know how to map to a
field class.
Test Utilities¶
playhouse.test_utils provides helpers for testing peewee projects.
- class count_queries(only_select=False)¶
Context manager that counts the number of SQL queries executed within its block.
- Parameters
only_select (bool) – If
True, count onlySELECTqueries.
with count_queries() as counter: user = User.get(User.username == 'alice') tweets = list(user.tweets) # Triggers a second query. assert counter.count == 2
- count¶
Number of queries executed.
- get_queries()¶
Return the executed queries as a list of
logging.LogRecordobjects. Each record’smsgattribute is the(sql, params)tuple.
- assert_query_count(expected, only_select=False)¶
Decorator or context manager that raises
AssertionErrorif the number of queries executed does not matchexpected.As a decorator:
class TestAPI(unittest.TestCase): @assert_query_count(1) def test_get_user(self): user = User.get_by_id(1)
As a context manager:
with assert_query_count(3): result = my_function_that_should_make_exactly_three_queries()