PEEWEE(1)

NAME
peewee - peewee Documentation [image]

Peewee is a simple and small ORM. It has few (but expressive) concepts, making it easy to learn and intuitive to use.

•   a small, expressive ORM

•   flexible query-builder that exposes full power of SQL

•   supports sqlite, mysql, mariadb, postgresql.

•   asyncio support

•   tons of extensions

•   use with flask <#flask>, fastapi <#fastapi> and more <#framework-integration>

    Peewee's source code hosted on GitHub <https://github.com/coleifer/peewee>.

    New to peewee? These may help:

•   Quickstart <#quickstart>

•   Example twitter app <#example>

•   Using peewee interactively <#interactive>

•   Models and fields <#models>

•   Querying <#querying>

•   Relationships and joins <#relationships>

•   Extensive library of SQL / Peewee examples <#query-library>

•   Flask setup <#flask> or FastAPI setup <#fastapi>

CONTENTS:

  Installing and Testing

Install the latest release from PyPI:

    pip install peewee

Peewee has an optional Sqlite C extension which is not bundled in the default wheel. It provides user-defined ranking functions for use with Sqlite FTS4 and functions for fuzzy string matching. To build from source:

    pip install peewee --no-binary :all:

  Installing from Source

    git clone https://github.com/coleifer/peewee.git
    cd peewee
    pip install .

  Running Tests

    python runtests.py
    python runtests.py --help  # Show options.

To run tests against Postgres or MySQL create a database named peewee_test. For the Postgres extension tests, enable hstore:

    CREATE EXTENSION hstore;

  Supported Drivers

Peewee works with any database for which a DB-API 2.0 driver exists. The following drivers are supported out of the box:
Database | Driver | Implementation
Sqlite | sqlite3 | SqliteDatabase <#SqliteDatabase>
Postgres | psycopg3 | PostgresqlDatabase <#PostgresqlDatabase>
Postgres | psycopg2 | PostgresqlDatabase <#PostgresqlDatabase>
MySQL | pymysql | MySQLDatabase <#MySQLDatabase>
Sqlite (async) | aiosqlite | AsyncSqliteDatabase <#playhouse.pwasyncio.AsyncSqliteDatabase>
Postgres (async) | asyncpg | AsyncPostgresqlDatabase <#playhouse.pwasyncio.AsyncPostgresqlDatabase>
MySQL (async) | aiomysql | AsyncMySQLDatabase <#playhouse.pwasyncio.AsyncMySQLDatabase>
Sqlite (alternate) | cysqlite | CySqliteDatabase <#playhouse.cysqlite_ext.CySqliteDatabase>
Sqlite (alternate) | apsw | APSWDatabase <#playhouse.apsw_ext.APSWDatabase>
SqlCipher | sqlcipher3 | SqlCipherDatabase <#playhouse.sqlcipher_ext.SqlCipherDatabase>
MySQL (alternate) | mysql-connector | MySQLConnectorDatabase <#playhouse.mysql_ext.MySQLConnectorDatabase>
MariaDB (alternate) | mariadb-connector | MariaDBConnectorDatabase <#playhouse.mysql_ext.MariaDBConnectorDatabase>
CockroachDB | psycopg (2 or 3) | CockroachDatabase <#playhouse.cockroachdb.CockroachDatabase>
Postgres (extensions) | psycopg (2 or 3) | PostgresqlExtDatabase <#playhouse.postgres_ext.PostgresqlExtDatabase>

The three bolded rows cover the majority of deployments. All others are optional; install their drivers when needed.

  Quickstart

This guide walks through defining a schema, writing rows, and reading them back. It takes about ten minutes. Every concept introduced here is covered in depth in the following documents.

Tip: Follow along in an interactive Python session.

  Model Definition

A Peewee application starts with a Database <#Database> object and one or more Model <#Model> classes. The database object manages connections; model classes map to tables.

    import datetime
    from peewee import *
    # An in-memory SQLite database. Or use PostgresqlDatabase or MySQLDatabase.
    db = SqliteDatabase(':memory:')
    class BaseModel(Model):
        """All models inherit this to share the database connection."""
        class Meta:
            database = db
    class User(BaseModel):
        username = TextField(unique=True)
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(
            default=datetime.datetime.now,
            index=True)

Three things to notice:

•   BaseModel exists only to carry the database setting. Every subclass inherits it automatically.

•   Peewee adds an auto-incrementing integer id primary key to any model that does not declare its own.

•   ForeignKeyField links Tweet to User. The backref='tweets' parameter means every User instance gains a tweets attribute.

  Create the Tables

    db.connect()
    db.create_tables([User, Tweet])

create_tables() <#Database.create_tables> generates CREATE TABLE statements for each model. By default create_table() specifies safe=True, which uses CREATE TABLE IF NOT EXISTS, making it safe to call on every startup.

  Writing Data

Create a row with create() <#Model.create> (one step) or instantiate a model and call save() <#Model.save> (two steps):

    # One-step creation - returns the saved instance.
    charlie = User.create(username='charlie')
    huey = User.create(username='huey')
    # Two-step creation.
    t = Tweet(user=charlie, content='Hello, world!')
    t.save()
    Tweet.create(user=charlie, content='My second tweet.')
    Tweet.create(user=huey, content='meow')

To update an existing row, modify attributes and call save() again:

    charlie.username = 'charlie_admin'
    charlie.save()

To delete a row:

    stale_tweet = Tweet.get(Tweet.content == 'My second tweet.')
    stale_tweet.delete_instance()

  Reading Data

Retrieve a single row with get() <#Model.get>. It raises DoesNotExist if no match is found:

    user = User.get(User.username == 'charlie_admin')
    print(user.id, user.username)

Retrieve multiple rows with select() <#Model.select>. The result is a lazy query - rows are fetched only when you iterate:

    for tweet in Tweet.select():
        print(tweet.content)

Filter with where() <#Query.where>:

    for tweet in Tweet.select().where(Tweet.user == charlie):
        print(tweet.content)
    for tweet in Tweet.select().where(Tweet.timestamp.year == 2026):
        print(tweet.content)

Sort with order_by() <#Query.order_by>:

    for tweet in Tweet.select().order_by(Tweet.timestamp.desc()):
        print(tweet.timestamp, tweet.content)

Join to combine data from related tables in a single query:

    # Fetch each tweet alongside its author's username.
    # Without the join, accessing tweet.user.username would issue
    # an extra query per tweet - see the N+1 section in Relationships.
    query = (Tweet
             .select(Tweet, User)
             .join(User)
             .order_by(Tweet.timestamp.desc()))
    for tweet in query:
        print(tweet.user.username, '->', tweet.content)

  Simple Aggregates

How many tweets are in the database:

    Tweet.select().count()

When the most-recent tweet was added:

    Tweet.select(fn.MAX(Tweet.timestamp)).scalar()

  Close the Connection

When done using the database, close the connection:

    db.close()

In a web application you would open the connection when a request arrives and close it when the response is sent. See Framework Integration <#framework-integration> for framework-specific patterns.

  Working with Existing databases

If you have an existing database, peewee can generate models using pwiz - Model Generator <#pwiz>. For example to generate models for a Postgres database named blog_db:

    python -m pwiz -e postgresql blog > blog_models.py

  What Next

Each concept introduced above is covered in full detail in the following documents:

•   Database <#database> - connection options, multiple backends, run-time configuration, connection pooling.

•   Models and Fields <#models> - field types, field parameters, model Meta options, indexes, primary keys.

•   Relationships and Joins <#relationships> - how foreign keys work at runtime, joins, the N+1 problem, many-to-many relationships.

•   Querying <#querying> - the full SELECT API: filtering, sorting, aggregates, window functions, CTEs.

•   Writing Data <#writing> - INSERT, UPDATE, DELETE, bulk operations, upsert.

•   Transactions <#transactions> - atomic blocks, nesting, savepoints.

    For a complete worked example building a small web application, see Example app <#example>.

  Example app

We'll be building a simple twitter-like site. The source code for the example can be found in the examples/twitter directory. You can also browse the source-code <https://github.com/coleifer/peewee/tree/master/examples/twitter> on github.

Tip: There is also an example blog app <https://github.com/coleifer/peewee/tree/master/examples/blog>, however it is not covered in this guide.

The example app uses the flask <https://flask.palletsprojects.com/en/stable/> web framework. You will need to install it to run the example:

    pip install flask

  Running the Example
[image]

After ensuring that flask is installed, cd into the twitter example directory and execute the run_example.py script:

    python run_example.py

The example app will be accessible at <http://localhost:5000/>

  Code Structure

For simplicity all example code is contained within a single module, examples/twitter/app.py. For a guide on structuring larger Flask apps with peewee, check out Structuring Flask Apps <https://charlesleifer.com/blog/structuring-flask-apps-a-how-to-for-those-coming-from-django/>.

  Models

peewee uses declarative model definitions. Declare a model class for each table. The model class then defines one or more field attributes which correspond to the table's columns. For the twitter clone, there are three models:

User:
    Represents a user account and stores the username and password, an email address for generating avatars using gravatar and a datetime field indicating when that account was created.

Relationship:
    This is a utility model that contains two foreign-keys to the User model and stores which users follow one another.

Message:
    Analogous to a tweet. The Message model stores the text content of the tweet, when it was created, and who posted it (foreign key to User).

    If you like UML, these are the tables and relationships: [image]

    In order to create these models we need:

1.  declare a SqliteDatabase <#SqliteDatabase> object

2.  declare our model classes

3.  declare columns as Field <#Field> instances on the model classes

        # create a peewee database instance -- our models will use this database to
        # persist information
        database = SqliteDatabase(DATABASE)
        # model definitions -- the standard "pattern" is to define a base model class
        # that specifies which database to use.  then, any subclasses will automatically
        # use the correct storage.
        class BaseModel(Model):
            class Meta:
                database = database
        # the user model specifies its fields (or columns) declaratively, like django
        class User(BaseModel):
            username = CharField(unique=True)
            password = CharField()
            email = CharField()
            join_date = DateTimeField()
        # this model contains two foreign keys to user -- it essentially allows us to
        # model a "many-to-many" relationship between users.  by querying and joining
        # on different columns we can expose who a user is "related to" and who is
        # "related to" a given user
        class Relationship(BaseModel):
            from_user = ForeignKeyField(User, backref='relationships')
            to_user = ForeignKeyField(User, backref='related_to')
            class Meta:
                # `indexes` is a tuple of 2-tuples, where the 2-tuples are
                # a tuple of column names to index and a boolean indicating
                # whether the index is unique or not.
                indexes = (
                    # Specify a unique multi-column index on from/to-user.
                    (('from_user', 'to_user'), True),
                )
        # a dead simple one-to-many relationship: one user has 0..n messages, exposed by
        # the foreign key. a users messages will be accessible as a special attribute,
        # User.messages.
        class Message(BaseModel):
            user = ForeignKeyField(User, backref='messages')
            content = TextField()
            pub_date = DateTimeField()

    Note: Note that we create a BaseModel class that simply defines what database we would like to use. All other models then extend this class and will also use the correct database connection.

    Peewee supports many different field types <#fields> which map to different column types commonly supported by database engines. Conversion between python types and those used in the database is handled transparently, allowing you to use the following in your application:

•   Strings (unicode or otherwise)

•   Integers, floats, and Decimal numbers.

•   Boolean values

•   Dates, times and datetimes

•   None (NULL)

•   Binary data

  Creating Tables

In order to start using the models, its necessary to create the tables. This is a one-time operation and can be done quickly using the interactive interpreter. We can create a small helper function to accomplish this:

    def create_tables():
        with database:
            database.create_tables([User, Relationship, Message])

Open a python shell in the directory alongside the example app and execute the following:

    >>> from app import *
    >>> create_tables()

Attention: If you encounter an ImportError it means that either flask or peewee was not found and may not be installed correctly. Check the Installing and Testing <#installation> document for instructions on installing peewee.

Every model has a create_table() <#Model.create_table> classmethod which runs a SQL CREATE TABLE statement in the database. This method will create the table, including:

•   columns

•   foreign-key constraints

•   indexes

•   sequences

•   check constraints

    Usually this is something you'll only do once, when a new model is added.

    Peewee provides a helper method Database.create_tables() <#Database.create_tables> which will resolve inter-model dependencies and call create_table() <#Model.create_table> on each model, ensuring the tables are created in order.

    Note: Adding, removing or modifying fields after the table has been created will require you to either:

•   drop the table and re-create it, OR

•   manually add, drop or modify the columns, OR

•   use the migration tools <#migrate> to script your changes.

  Database Connection

You may have noticed in the above model code that there is a class defined on the base model named Meta that sets the database attribute. Peewee allows every model to specify which database it uses. There are many Meta options <#model-options> you can specify which control the behavior of your model.

This is a peewee idiom:

    DATABASE = 'tweepee.db'
    # Create a database instance that will manage the connection and
    # execute queries
    database = SqliteDatabase(DATABASE)
    # Create a base-class all our models will inherit, which defines
    # the database we'll be using.
    class BaseModel(Model):
        class Meta:
            database = database

When developing a web application, it's common to:

1.  Open a connection when a request starts.

2.  Run your request-handler.

3.  Close the connection before returning the response.

    You should always manage your connections explicitly. For instance, if you are using a connection pool <#pool>, connections will only be recycled correctly if you call connect() <#Database.connect> and close() <#Database.close>.

    Flask provides connection setup/teardown hooks via decorators:

        @app.before_request
        def before_request():
            database.connect()
        @app.teardown_request
        def teardown_request(exc):
            if not database.is_closed():
                database.close()

    See also: Framework Integration <#framework-integration> covers setting-up hooks for a variety of popular web frameworks.

    Note: Peewee uses thread local storage to manage connection state, so this pattern can be used with multi-threaded or gevent WSGI servers.

    Peewee's asyncio integration <#asyncio> stores connection state in task-local storage, so the same pattern applies.

  Making Queries

In the User model there are a few instance methods that encapsulate some user-specific functionality:

•   following(): who is this user following?

•   followers(): who is following this user?

    These methods are similar in their implementation but with an important difference in the SQL JOIN and WHERE clauses:

        def following(self):
            return (User
                    .select()
                    .join(Relationship, on=Relationship.to_user)
                    .where(Relationship.from_user == self)
                    .order_by(User.username))
        def followers(self):
            return (User
                    .select()
                    .join(Relationship, on=Relationship.from_user)
                    .where(Relationship.to_user == self)
                    .order_by(User.username))

  Storing Data

When a new user wants to join the site we need to make sure the username is available, and if so, create a new User record. Looking at the join() view, we can see that our application attempts to create the User using Model.create() <#Model.create>. User.username field has a unique constraint, so if the username is taken the database will raise an IntegrityError <#IntegrityError>.

    try:
        with database.atomic():
            # Attempt to create the user. If the username is taken, due to the
            # unique constraint, the database will raise an IntegrityError.
            user = User.create(
                username=request.form['username'],
                password=md5(request.form['password']).hexdigest(),
                email=request.form['email'],
                join_date=datetime.datetime.now())
        # mark the user as being 'authenticated' by setting the session vars
        auth_user(user)
        return redirect(url_for('homepage'))
    except IntegrityError:
        flash('That username is already taken')

We will use a similar approach when a user wishes to follow someone. To indicate a following relationship, we create a row in the Relationship table pointing from one user to another. Due to the unique index on (from_user, to_user), we will be sure not to end up with duplicate rows:

    user = get_object_or_404(User, username=username)
    try:
        with database.atomic():
            Relationship.create(
                from_user=get_current_user(),
                to_user=user)
    except IntegrityError:
        pass

  Subqueries

If you are logged-in and visit the twitter homepage, you will see tweets from the users that you follow. In order to implement this cleanly, we can use a subquery:

Note: user.following() will automatically only select User.id when it used in a subquery.

    # python code
    user = get_current_user()
    messages = (Message
                .select()
                .where(Message.user.in_(user.following()))
                .order_by(Message.pub_date.desc()))

This code corresponds to the following SQL query:

    SELECT t1."id", t1."user_id", t1."content", t1."pub_date"
    FROM "message" AS t1
    WHERE t1."user_id" IN (
        SELECT t2."id"
        FROM "user" AS t2
        INNER JOIN "relationship" AS t3
            ON t2."id" = t3."to_user_id"
        WHERE t3."from_user_id" = ?
    )

  Other Topics

There are a couple other neat things going on in the example app that are worth mentioning briefly.

•   Support for paginating lists of results is implemented in a simple function called object_list. This function is used by all the views that return lists of objects.

        def object_list(template_name, qr, var_name='object_list', **kwargs):
            kwargs.update(
                page=int(request.args.get('page', 1)),
                pages=qr.count() / 20 + 1)
            kwargs[var_name] = qr.paginate(kwargs['page'])
            return render_template(template_name, **kwargs)

•   Simple authentication system with a login_required decorator. The first function simply adds user data into the current session when a user successfully logs in. The decorator login_required can be used to wrap view functions, checking for whether the session is authenticated and if not redirecting to the login page.

        def auth_user(user):
            session['logged_in'] = True
            session['user'] = user
            session['username'] = user.username
            flash('You are logged in as %s' % (user.username))
        def login_required(f):
            @wraps(f)
            def inner(*args, **kwargs):
                if not session.get('logged_in'):
                    return redirect(url_for('login'))
                return f(*args, **kwargs)
            return inner

•   Return a 404 response instead of throwing exceptions when an object is not found in the database.

        def get_object_or_404(model, *expressions):
            try:
                return model.get(*expressions)
            except model.DoesNotExist:
                abort(404)

    Tip: To avoid having to frequently copy/paste object_list() or get_object_or_404(), these functions are included as part of the playhouse flask extension module <#flask-utils>.

        from playhouse.flask_utils import get_object_or_404, object_list

  More Examples

There are more examples included in the peewee examples directory <https://github.com/coleifer/peewee/blob/master/examples/>, including:

•   Example blog app <https://github.com/coleifer/peewee/tree/master/examples/blog> using Flask and peewee. Also see accompanying blog post <https://charlesleifer.com/blog/how-to-make-a-flask-blog-in-one-hour-or-less/>.

•   An encrypted command-line diary <https://github.com/coleifer/peewee/blob/master/examples/diary.py>. There is a companion blog post <https://charlesleifer.com/blog/dear-diary-an-encrypted-command-line-diary-with-python/> you might enjoy as well.

•   Analytics web-service <https://github.com/coleifer/peewee/tree/master/examples/analytics> (like a lite version of Google Analytics). Also check out the companion blog post <https://charlesleifer.com/blog/saturday-morning-hacks-building-an-analytics-app-with-flask/>.

    See also: Like these snippets and interested in more? Check out flask-peewee <https://github.com/coleifer/flask-peewee> - a flask plugin that provides a django-like Admin interface, RESTful API, Authentication and more for your peewee models.

  Database

The Peewee Database <#Database> object represents a connection to a database. The Database <#Database> class is instantiated with all the information needed to connect to a database.

Database responsibilities:

•   Connection Lifecycle

•   Executing SQL

•   Manage database schema <#schema>

•   Manage transactions <#transactions>

    Peewee supports:

•   SQLite - SqliteDatabase <#SqliteDatabase> using the standard library sqlite3.

        # SQLite database (use WAL journal mode and 64MB cache).
        db = SqliteDatabase('/path/to/app.db', pragmas={
            'journal_mode': 'wal',
            'cache_size': -64000})

•   Postgresql - PostgresqlDatabase <#PostgresqlDatabase> using psycopg2 or psycopg3.

        db = PostgresqlDatabase(
            'my_app',
            user='postgres',
            password='secret',
            host='10.8.0.9',
            port=5432)

•   MySQL and MariaDB - MySQLDatabase <#MySQLDatabase> using pymysql.

        db = MySQLDatabase(
            'my_app',
            user='app',
            password='db_password',
            host='10.8.0.8',
            port=3306)

  Using SQLite

To connect to a SQLite database, use SqliteDatabase <#SqliteDatabase>. The first parameter is the filename containing the database, or the string ':memory:' to create an in-memory database.

After the database filename specify pragmas or other sqlite3 parameters <https://docs.python.org/3/library/sqlite3.html#sqlite3.connect>.

    db = SqliteDatabase('my_app.db', pragmas={'journal_mode': 'wal'})
    class BaseModel(Model):
        """Base model that will use our Sqlite database."""
        class Meta:
            database = db
    class User(BaseModel):
        username = TextField()
        ...

SQLite-specific options are set via pragmas <https://www.sqlite.org/pragma.html>. The following settings are recommended for most applications:

    db = SqliteDatabase('my_app.db', pragmas={
        'journal_mode': 'wal',  # Allow readers while writer active.
        'cache_size': -64000,  # 64 MB page cache.
        'foreign_keys': 1,  # Enforce FK constraints.
    })
Pragma | Recommended value | Effect
journal_mode | wal | Allow concurrent readers and one writer.
cache_size | Negative KiB value | E.g. -64000 = 64 MB.
foreign_keys | 1 | Enforce FOREIGN KEY constraints.

See also: For SQLite-specific features and extensions (JSON, full-text search), see SQLite <#sqlite>.

  Using Postgresql

To use Peewee with Postgresql install psycopg2 or psycopg3:

    pip install "psycopg2-binary"  # Psycopg2.
    pip install "psycopg[binary]"  # Psycopg3.

To connect to a Postgresql database, use PostgresqlDatabase <#PostgresqlDatabase>. The first parameter is always the name of the database.

After the database name specify additional psycopg2 <https://www.psycopg.org/docs/module.html#psycopg2.connect> or psycopg3 <https://www.psycopg.org/psycopg3/docs/api/module.html#psycopg.connect> connection parameters:

    db = PostgresqlDatabase(
        'my_database',
        user='postgres',
        password='secret',
        host='10.8.0.1',
        port=5432)
    class BaseModel(Model):
        """A base model that will use our Postgresql database"""
        class Meta:
            database = db
    class User(BaseModel):
        username = CharField()
        ...

The isolation level can be set at initialization time:

    # psycopg2 or psycopg3
    db = PostgresqlDatabase('app', isolation_level='SERIALIZABLE')
    # psycopg2
    from psycopg2.extensions import ISOLATION_LEVEL_SERIALIZABLE
    db = PostgresqlDatabase('app', isolation_level=ISOLATION_LEVEL_SERIALIZABLE)
    # psycopg3
    from psycopg import IsolationLevel
    db = PostgresqlDatabase('app', isolation_level=IsolationLevel.SERIALIZABLE)

See also: For Postgresql-specific functionality and extensions (arrays, JSONB, full-text search), see Postgresql <#postgresql>.

  Using MySQL / MariaDB

To use Peewee with MySQL or MariaDB install pymysql:

    pip install pymysql

To connect to a MySQL or MariaDB database, use MySQLDatabase <#MySQLDatabase>. The first parameter is always the name of the database.

After the database name specify additional pymysql Connection parameters <https://pymysql.readthedocs.io/en/latest/modules/connections.html>:

    db = MySQLDatabase(
        'my_database',
        host='10.8.0.1',
        port=3306,
        connection_timeout=5)
    class BaseModel(Model):
        """A base model that will use our MySQL database"""
        class Meta:
            database = mysql_db
    class User(BaseModel):
        username = CharField()
        # ...

If MySQL drops idle connections (Error 2006: MySQL server has gone away), the solution is explicit connection management: open a connection at the start of each unit of work and close it when finished. See Connection Lifecycle and Framework Integration <#framework-integration>.

Alternate drivers are available for both databases:

•   MySQLConnectorDatabase <#playhouse.mysql_ext.MySQLConnectorDatabase> - uses mysql-connector-python.

•   MariaDBConnectorDatabase <#playhouse.mysql_ext.MariaDBConnectorDatabase> - uses mariadb-connector-python.

    See also: For MySQL-specific functionality and extensions, see MySQL and MariaDB <#mysql>.

  Connection Parameters

Database <#Database> initialization methods expect the name of the database as the first parameter. Subsequent keyword arguments are passed to the underlying database driver when establishing the connection.

With Postgresql it is common to need to specify the host, user and password when creating a connection. These should be specified when initializing the database, and they will be passed directly back to psycopg when creating connections:

    db = PostgresqlDatabase(
        'database_name',  # Required by Peewee.
        user='postgres',  # Will be passed directly to psycopg.
        password='secret',  # Ditto.
        host='db.mysite.com')  # Ditto.

As another example, the pymysql driver accepts a charset parameter which is not a standard Peewee Database <#Database> parameter. To set this value, pass in charset alongside your other settings:

    db = MySQLDatabase('database_name', user='www-data', charset='utf8mb4')

Consult your database driver's documentation for the available parameters:

•   Postgresql: psycopg2 <https://www.psycopg.org/docs/module.html#psycopg2.connect> or psycopg3 <https://www.psycopg.org/psycopg3/docs/api/module.html#psycopg.connect>

•   MySQL: pymysql <https://github.com/PyMySQL/PyMySQL/blob/f08f01fe8a59e8acfb5f5add4a8fe874bec2a196/pymysql/connections.py#L494-L513>

•   SQLite: sqlite3 <https://docs.python.org/3/library/sqlite3.html#sqlite3.connect>

  Initializing the Database

There are three ways to initialize a database:

1.  Initialize database directly. Use when connection settings are available at the time the database is declared:

        db = SqliteDatabase('/path/to/app.db')

    Environment variables, config settings, etc. typically fall into this category as well:

        import os
        db = PostgresqlDatabase(
            database=app.config['APP_NAME'],
            user=os.environ.get('PGUSER') or 'postgres',
            host=os.environ.get('PGHOST') or '127.0.0.1')

2.  Defer initialization. This method is needed when a connection setting is not available until run-time or it is inconvenient to import connection settings where the database is declared:

        db = PostgresqlDatabase(None)
        # ... some time later ...
        db_name = input('Enter database name: ')
        # Initialize the database now.
        db.init(db_name, user='postgres', host='10.8.0.1')

    Attempting to use an uninitialized database will raise an InterfaceError <#InterfaceError>.

3.  Proxy. Use a DatabaseProxy <#DatabaseProxy> and set the database at run-time. This method is needed when the database implementation may change at run-time. For example it may be either Sqlite or Postgresql depending on a command-line option:

        db = DatabaseProxy()
        # ... some time later ...
        if app.config['DEBUG']:
            database = SqliteDatabase('local.db')
        elif app.config['TESTING']:
            database = SqliteDatabase(':memory:')
        else:
            database = PostgresqlDatabase('production')
        db.initialize(database)

    Attempting to use an uninitialized database proxy will raise an AttributeError.

  Changing the database at run-time

Peewee can also set or change the database at run-time in a different way. This technique is used by the Peewee test suite to bind test model classes to various database instances when running tests.

There are two sets of complementary methods:

•   Database.bind() <#Database.bind> and Model.bind() <#Model.bind> - bind one or more models to a database.

•   Database.bind_ctx() <#Database.bind_ctx> and Model.bind_ctx() <#Model.bind_ctx> - which are the same as their bind() counterparts, but return a context-manager and are useful when the database should only be changed temporarily.

    As an example, we'll declare two models without specifying any database:

        class User(Model):
            username = TextField()
        class Tweet(Model):
            user = ForeignKeyField(User, backref='tweets')
            content = TextField()
            timestamp = TimestampField()

    Bind the models to a database at run-time:

        postgres_db = PostgresqlDatabase('my_app', user='postgres')
        sqlite_db = SqliteDatabase('my_app.db')
        # At this point, the User and Tweet models are NOT bound to any database.
        # Bind them to the Postgres database:
        postgres_db.bind([User, Tweet])
        # Temporarily bind them to the sqlite database:
        with sqlite_db.bind_ctx([User, Tweet]):
            # User and Tweet are now bound to the sqlite database.
            assert User._meta.database is sqlite_db
        # User and Tweet are once again bound to the Postgres database.
        assert User._meta.database is postgres_db

    The Model.bind() <#Model.bind> and Model.bind_ctx() <#Model.bind_ctx> methods work the same for binding a given model class:

        # Bind the user model to the sqlite db. By default, Peewee will also
        # bind any models that are related to User via foreign-key as well.
        User.bind(sqlite_db)
        assert User._meta.database is sqlite_db
        assert Tweet._meta.database is sqlite_db  # Related models bound too.
        # Temporarily bind *just* the User model to the postgres db.
        with User.bind_ctx(postgres_db, bind_backrefs=False):
            assert User._meta.database is postgres_db
            assert Tweet._meta.database is sqlite_db  # Has not changed.
        # User is back to being bound to the sqlite_db.
        assert User._meta.database is sqlite_db

    Note: Peewee database connections are thread-safe. However, if you plan to bind the database at run-time in a multi-threaded application, storing the model's database in a thread-local is necessary. This can be accomplished with the ThreadSafeDatabaseMetadata <#playhouse.shortcuts.ThreadSafeDatabaseMetadata>.

        from peewee import *
        from playhouse.shortcuts import ThreadSafeDatabaseMetadata
        class BaseModel(Model):
            class Meta:
                model_metadata_class = ThreadSafeDatabaseMetadata

    The database can now be swapped safely while running in a multi-threaded environment using the Database.bind() <#Database.bind> or Database.bind_ctx() <#Database.bind_ctx>.

  Connecting via URL

The Database URLs <#db-url> playhouse module provides a connect() <#playhouse.db_url.connect> helper that accepts a database URL and returns the appropriate database instance:

    from playhouse.db_url import connect
    db = connect(os.environ.get('DATABASE_URL', 'sqlite:///local.db'))

Example URLs:

•   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://user:password@host:5432/dbname

•   mysql://user:password@host:3306/dbname

•   More examples in the db_url documentation <#db-url>.

  Connection Lifecycle

Applications will generally fall into two categories:

•   Single-user applications which open a connection at startup and close at exit.

•   Multi-user or web appliactions, which open a connection per request and close it at the end of the request.

    To open a connection to a database, use the Database.connect() <#Database.connect> method:

        >>> db = SqliteDatabase(':memory:')  # In-memory SQLite database.
        >>> db.connect()
        True
        >>> pass  # ... do work ...
        >>> db.close()
        True

    If you call connect() on an already-open database, an OperationalError <#OperationalError> is raised. Pass reuse_if_open=True to suppress it:

        >>> db.connect(reuse_if_open=True)

    To close a connection, use the Database.close() <#Database.close> method:

        >>> db.close()
        True

    Calling close() on an already-closed connection will not result in an exception, but will return False:

        >>> db.connect()  # Open connection.
        True
        >>> db.close()  # Close connection.
        True
        >>> db.close()  # Connection already closed, returns False.
        False

    Determine whether the database is closed using the Database.is_closed() <#Database.is_closed> method:

        >>> db.is_closed()
        True

    Web applications will typically use framework-provided hooks to manage connection lifecycles.

        @app.before_request
        def _db_connect():
            db.connect()
        @app.teardown_request
        def _db_close(exc):
            if not db.is_closed():
                db.close()

    See Framework Integration <#framework-integration> for framework-specific examples.

    Tip: Peewee uses thread local storage to manage connection state, so this pattern can be used with multi-threaded or gevent applications.

    Peewee's asyncio integration <#asyncio> stores connection state in task-local storage, so the same pattern applies.

  Context managers

The database object can be used as a context manager or decorator.

1.  Connection opens when context manager is entered.

2.  Peewee begins a transaction.

3.  Control is passed to user for duration of block.

4.  Peewee commits transaction if block exits cleanly, otherwise issues a rollback.

5.  Peewee closes the connection.

6.  Any unhandled exception is raised.

        with db:
            User.create(username='charlie')
            # Transaction is committed when the block exits normally,
            # rolled back if an exception is raised.

    Decorator:

        @db
        def demo():
            print('closed?', db.is_closed())
        demo()  # "closed? False"
        db.is_closed()  # True

    To manage the connection lifetime without an implicit transaction, use connection_context() <#Database.connection_context>:

        with db.connection_context():
            # Connection is open; no implicit transaction.
            results = User.select()

    connection_context() can also decorate a function:

        @db.connection_context()
        def load_fixtures():
            db.create_tables([User, Tweet])
            import_data()

  Using autoconnect

By default Peewee will automatically open a connection if one is not available. This behavior is controlled by the autoconnect Database parameter. Managing connections explicitly is considered a best practice, therefore consider disabling the autoconnect behavior:

    db = PostgresqlDatabase('app', autoconnect=False)

It is helpful to be explicit about connection lifetimes. If a connection cannot be opened, the exception will be caught when the connection is being opened, rather than at query time.

  Thread safety

Database connections and associated transactions are thread-safe.

Peewee keeps track of the connection state using thread-local storage, making the Peewee Database <#Database> object safe to use with multiple threads. Each thread will have it's own connection, and as a result any given thread will only have a single connection open at a given time.

Peewee's asyncio integration <#asyncio> stores connection state in task-local storage, so the same applies to async applications.

  DB-API Connection object

Database.connection() <#Database.connection> returns a reference to the underlying DB-API driver connection. This method will return the currently-open connection object, if one exists, otherwise it will open a new connection.

    >>> db.connection()
    <sqlite3.Connection object at 0x7f94e9362f10>

  Connection Pooling

For web applications that handle many requests, opening and closing a database connection on every request adds latency. A connection pool keeps a set of connections open and lends them out as needed.

Pooled database classes are available in playhouse.pool <#pool>:

    from playhouse.pool import PooledPostgresqlDatabase
    db = PooledPostgresqlDatabase(
        'my_app',
        user='postgres',
        max_connections=20,
        stale_timeout=300,   # Recycle connections idle for 5 minutes.
    )

Commonly-used pool implementations:

•   PooledPostgresqlDatabase

•   PooledMySQLDatabase

•   PooledSqliteDatabase

    Additional implementations:

•   playhouse.cysqlite_ext - PooledCySqliteDatabase

•   playhouse.mysql_ext - PooledMariaDBConnectorDatabase

•   playhouse.mysql_ext - PooledMySQLConnectorDatabase

•   playhouse.postgres_ext - PooledPostgresqlExtDatabase

•   playhouse.postgres_ext - PooledPsycopg3Database

•   playhouse.cockroachdb - PooledCockroachDatabase

    When using a connection pool, connect() <#Database.connect> and close() <#Database.close> do not open and close real connections - they acquire and release connections from the pool. It is therefore essential to call both explicitly (or use a context manager) so connections are returned to the pool for re-use.

    See also: Connection Pooling <#pool>

  Executing SQL

SQL queries will typically be executed by calling execute() on a query constructed using the query-builder APIs (or by simply iterating over a query object in the case of a Select <#Select> query). For cases where you wish to execute SQL directly, use the Database.execute_sql() <#Database.execute_sql>:

    db = SqliteDatabase('my_app.db')
    db.connect()
    # Example of executing a simple query and ignoring the results.
    db.execute_sql("ATTACH DATABASE ':memory:' AS cache;")
    # Example of iterating over the results of a query using the cursor.
    cursor = db.execute_sql('SELECT * FROM users WHERE status = ?', (ACTIVE,))
    for row in cursor.fetchall():
        # Do something with row, which is a tuple containing column data.
        pass

  Database Errors

The Python DB-API 2.0 spec describes several types of exceptions <https://www.python.org/dev/peps/pep-0249/#exceptions>. Because most database drivers have their own implementations of these exceptions, Peewee simplifies things by providing its own wrappers around any implementation-specific exception classes. That way, you don't need to worry about dealing with driver-specific exception classes, you can just use the ones from peewee:

•   DatabaseError <#DatabaseError>

•   DataError <#DataError>

•   IntegrityError <#IntegrityError>

•   InterfaceError <#InterfaceError>

•   InternalError <#InternalError>

•   NotSupportedError <#NotSupportedError>

•   OperationalError <#OperationalError>

•   ProgrammingError <#ProgrammingError>

    Note: All of these error classes extend PeeweeException <#PeeweeException>.

  Logging Queries

Peewee logs every query to the peewee namespace at DEBUG level using the standard library logging module:

    import logging
    logging.getLogger('peewee').addHandler(logging.StreamHandler())
    logging.getLogger('peewee').setLevel(logging.DEBUG)

This is the simplest way to verify what queries are being issued during development.

  Testing Peewee Applications

When writing tests for an application that uses Peewee, it may be desirable to use a special database for tests. Another common practice is to run tests against a clean database, which means ensuring tables are empty at the start of each test.

Binding models to a database at run-time is described here: Changing the database at run-time.

Example test-case setup:

    # tests.py
    import unittest
    from my_app.models import EventLog, Relationship, Tweet, User
    MODELS = [User, Tweet, EventLog, Relationship]
    # use an in-memory SQLite for tests.
    test_db = SqliteDatabase(':memory:')
    class BaseTestCase(unittest.TestCase):
        def setUp(self):
            # Bind model classes to test db. Since we have a complete list of
            # all models, we do not need to recursively bind dependencies.
            test_db.bind(MODELS, bind_refs=False, bind_backrefs=False)
            test_db.connect()
            test_db.create_tables(MODELS)
        def tearDown(self):
            # Not strictly necessary since SQLite in-memory databases only live
            # for the duration of the connection, and in the next step we close
            # the connection...but a good practice all the same.
            test_db.drop_tables(MODELS)
            # Close connection to db.
            test_db.close()
            # If we wanted, we could re-bind the models to their original
            # database here. But for tests this is probably not necessary.

It is recommended to test using the same database backend used in production, so as to avoid any potential compatibility issues.

See also:

•   Test Utilities <#test-utils>

•   Peewee's test-suite <https://github.com/coleifer/peewee/tree/master/tests>

  Adding a Custom Database Driver

If your database driver conforms to DB-API 2.0, adding Peewee support requires subclassing Database <#Database> and overriding _connect, which must return a connection in autocommit mode:

    from peewee import Database
    import foodb
    class FooDatabase(Database):
        def _connect(self):
            return foodb.connect(self.database, autocommit=True,
                                 **self.connect_params)
        def get_tables(self):
            res = self.execute_sql('SHOW TABLES;')
            return [r[0] for r in res.fetchall()]

The minimum Peewee relies on from the driver is: Connection.commit, Connection.rollback, Connection.execute, Cursor.description, and Cursor.fetchone. Everything else can be incrementally added.

Other integration points on Database <#Database>:

•   param / quote - parameter placeholder and quoting characters.

•   field_types - mapping from Peewee type labels to vendor column types.

•   operations - mapping from operations such as ILIKE to vendor SQL.

    Refer to the Database <#Database> API reference or the Peewee source <https://github.com/coleifer/peewee/blob/master/peewee.py> for details.

  Models and Fields

Models and Fields allow Peewee applications to declare the tables and columns they will use, and issue queries using Python. This document explains how to use Peewee to express database tables and columns.

Model <#Model> classes, Field <#Field> instances and model instances all map to database concepts:
Python construct | Database concept
Model class | Table
Field instance | Column
Model instance | Row

Tip: If you are connecting Peewee to an existing database rather than defining a schema from scratch, the pwiz <#pwiz> tool can generate model definitions automatically by introspecting the database.

The following code shows the typical way you will define your database connection and model classes.

    import datetime
    from peewee import *
    db = SqliteDatabase('my_app.db')
    class BaseModel(Model):
        class Meta:
            database = db
    class User(BaseModel):
        username = CharField(unique=True)
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(default=datetime.datetime.now)
        is_published = BooleanField(default=True)
    class Favorite(BaseModel):
        user = ForeignKeyField(User, backref='favorites')
        tweet = ForeignKeyField(Tweet, backref='favorites')

Three things to note:

1.  Create an instance of a Database <#Database>.

        db = SqliteDatabase('my_app.db')

    The db object will be used to manage the connections to the Sqlite database. In this example we're using SqliteDatabase <#SqliteDatabase>, but you could also use one of the other database engines <#database>.

2.  Create a base model class which specifies our database.

        class BaseModel(Model):
            class Meta:
                database = db

    BaseModel exists only to specify the database setting in its Meta class. Because Meta.database is inheritable, every model that extends BaseModel will automatically use the same database. This pattern avoids repeating the database assignment on every model class.

    Model configuration is kept namespaced in a special class called Meta. Meta configuration is passed on to subclasses, so our project's models will all subclass BaseModel. There are many different attributes you can configure using Model.Meta.

3.  Declare model classes and fields.

        class User(BaseModel):
            username = CharField(unique=True)

    Model definition uses the declarative style seen in other popular ORMs. Note that we are extending the BaseModel class so the User model will inherit the database connection.

    We have explicitly defined a single username column with a unique constraint. Because we have not specified a primary key, Peewee will automatically add an auto-incrementing integer primary key field named id.

  Model Inheritance

Model subclasses inherit the Meta configuration of their parent as well as the parent's fields. Inherited Meta attributes (such as database) are shared; non-inheritable attributes (such as table_name) are re-derived for each subclass.

    class BaseModel(Model):
        class Meta:
            database = db
    class TimestampedModel(BaseModel):
        """Adds created/updated timestamps to any subclass."""
        created = DateTimeField(default=datetime.datetime.now)
        updated = DateTimeField(default=datetime.datetime.now)
    class Article(TimestampedModel):
        title = TextField()
        body = TextField()
        # Article.created and Article.updated are inherited.
        # Article._meta.database is inherited from BaseModel.

Peewee uses a separate table for each concrete model class. There is no notion of inheritance spanning multiple tables. If you subclass a model, both the parent and the child have their own tables.

  Fields

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

When creating a Model <#Model> class, fields are defined as class attributes:

    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(default=datetime.datetime.now)
        is_published = BooleanField(default=True)

In the above example, no field specifies primary_key=True. As a result, Peewee will create an auto-incrementing integer primary key named id. Peewee uses AutoField <#AutoField> to signify an auto-incrementing integer primary key.

  Field types
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

See also:

•   SQLite fields for JSON, Full-Text Search: SQLite <#sqlite>

•   Postgresql fields for Arrays, JSON, Full-Text Search, HStore: Postgresql <#postgresql>

•   MySQL fields for JSON: MySQL and MariaDB <#mysql>

•   Extra fields (extension): Extra Field Types <#extra-fields>

•   Custom fields

  Common field parameters

All field types accept the following keyword arguments:
Parameter | Default | Description
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
index_type | None | specify a custom index-type, e.g. for Postgres you might specify a 'BRIN' or 'GIN' index.

  Special parameters
Field type | Special Parameters
ForeignKeyField <#ForeignKeyField> | model, field, backref, on_delete, on_update, deferrable lazy_load
CharField <#CharField> | max_length
FixedCharField <#FixedCharField> | max_length
DateTimeField <#DateTimeField> | formats
DateField <#DateField> | formats
TimeField <#TimeField> | formats
TimestampField <#TimestampField> | resolution, utc
DecimalField <#DecimalField> | max_digits, decimal_places, auto_round, rounding
BareField <#BareField> | adapt

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:

    class Product(Model):
        price = DecimalField(max_digits=8, decimal_places=2,
                             constraints=[Check('price >= 0')])
        added = DateTimeField(constraints=[Default('CURRENT_TIMESTAMP')])
        status = IntegerField(constraints=[Check('status in (0, 1, 2)')])

  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:

    class Message(Model):
        context = TextField()
        read_count = IntegerField(default=0)
        created = DateTimeField(default=datetime.datetime.now)

For read_count, Peewee uses the literal value 0. For created, Peewee calls datetime.datetime.now at the moment of instantiation - note that the function itself is passed, not its return value.

Mutable defaults require a factory function. If a default value is a mutable object such as a list or dict, passing it directly means every model instance shares the same object. Wrap it in a function instead:

    # Wrong: all instances share one dict.
    class Config(BaseModel):
        settings = JSONField(default={})
    # Correct: each instance gets a fresh dict.
    def default_settings():
        return {}
    class Config(BaseModel):
        settings = JSONField(default=default_settings)

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 and Default() <#Default> to specify the server default:

    class Message(Model):
        content = TextField()
        timestamp = DateTimeField(constraints=[Default('CURRENT_TIMESTAMP')])

This produces a DEFAULT CURRENT_TIMESTAMP clause in the CREATE TABLE statement. Peewee's own default parameter produces no DDL; it only operates during Python-side model instantiation.

A consequence of using server-generated defaults is that newly-inserted models will not automatically retrieve the new value. This requires a separate query to read back the defaults added by the server.

  ForeignKeyField

ForeignKeyField <#ForeignKeyField> links a model to another model. It stores the related row's primary key as an integer column and provides a Python-level descriptor that resolves it to a full model instance on access.

    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()

The backref parameter creates a reverse accessor on the target model. With backref='tweets', every User instance gains a tweets attribute that returns a pre-filtered Select <#Select> query of that user's tweets.

ForeignKeyField <#ForeignKeyField> accepts referential action parameters:

•   on_delete - action to take when the referenced row is deleted. Common values: 'CASCADE', 'SET NULL', 'RESTRICT'.

•   on_update - action to take when the referenced row's primary key changes.

•   deferrable - defers constraint checking to transaction commit (Postgresql and SQLite only).

    Warning: SQLite does not enforce foreign key constraints by default. Enable enforcement by setting the foreign_keys pragma on connection:

        db = SqliteDatabase('my_app.db', pragmas={'foreign_keys': 1})

    See also: Relationships and Joins <#relationships> covers how foreign keys behave at runtime, including lazy loading, back-references, and avoiding N+1 query problems.

    Typically a foreign key will reference the primary key of the related model, but you can specify a particular column by specifying field=.

    In Peewee, accessing the value of a ForeignKeyField <#ForeignKeyField> will return the entire related object:

        tweets = (Tweet
                  .select(Tweet, User)
                  .join(User)
                  .order_by(Tweet.created_date.desc()))
        for tweet in tweets:
            print(tweet.user.username, tweet.message)

    In the example above the User data was selected efficiently. If we did not select the User, then an additional query would be needed 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 key column. Peewee allows you to access the raw foreign key value by appending "_id" to the foreign 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 additional query, ForeignKeyField <#ForeignKeyField> supports an initialization paramater lazy_load which, when disabled, behaves like the "_id" attribute:

        class Tweet(Model):
            # 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 <#ForeignKeyField> allows for a back-reference property to be bound to the target model. This property will be named <classname>_set by default, where classname is the lowercase name of the model class. This name can be overridden by specifying 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)

Back-references are just pre-filtered select queries, so we can add additional behavior like order_by():

    for message in some_user.inbox.order_by(Message.id):
        # Iterate over all Messages whose to_user is some_user.
        print(message)

  Self-referential foreign keys

When creating a hierarchical structure it is necessary to create a self-referential foreign key which links a child object to its parent. Because the model class is not defined at the time you instantiate the self-referential foreign key, use the special string 'self' to indicate a self-referential foreign key:

    class Category(Model):
        name = CharField()
        parent = ForeignKeyField('self', null=True, backref='children')

The foreign key points upward to the parent object and the back-reference is named children.

Attention: Self-referential foreign-keys should always be null=True.

When querying against a model that contains a self-referential foreign key you may sometimes need to perform a self-join. In those cases you can use Model.alias() <#Model.alias> to create a table reference. Here is how you might query the category and parent model using a self-join:

    Parent = Category.alias()
    GrandParent = Category.alias()
    query = (Category
             .select(Category, Parent)
             .join(Parent, on=(Category.parent == Parent.id))
             .join(GrandParent, on=(Parent.parent == GrandParent.id))
             .where(GrandParent.name == 'some category')
             .order_by(Category.name))

For deeply nested hierarchies, recursive CTEs are more efficient than repeated self-joins. See Common Table Expressions <#cte>.

See also: Relationships and Joins <#relationships>

  Date and Time Fields

The three fields devoted to working with dates and times have properties to access date attributes like year, month, hour, etc.

DateField <#DateField>
    Properties for: year, month, day

TimeField <#TimeField>
    Properties for: hour, minute, second

DateTimeField <#DateTimeField>:
    Properties for: year, month, day, hour, minute, second

    These properties can be used as an expression in a query. Let's say we have an events table and want to list all the days in the current month which have at least one event:

         # Get the current date.
         today = datetime.date.today()
         # Get days that have events for the current month.
         query = (Event
                  .select(Event.event_date.day.alias('day'))
                  .where(
                      (Event.event_date.year == today.year) &
                      (Event.event_date.month == today.month))
                  .distinct())
        # Group activity by hour of day.
        query = (PageView
                 .select(
                     PageView.timestamp.hour.alias('hour'),
                     fn.COUNT(PageView.id).alias('n'))
                 .group_by(PageView.timestamp.hour)
                 .order_by(PageView.timestamp.hour))

    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.

    TimestampField <#TimestampField> stores a datetime as a Unix timestamp integer. The resolution parameter controls sub-second precision (default: seconds); utc=True instructs Peewee to treat stored values as UTC.

  BitField and BigBitField

The BitField <#BitField> and BigBitField <#BigBitField> are suitable for storing bitmap data. BitField <#BitField> provides a subclass of IntegerField <#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 <#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 <#BooleanField> objects, or we could use a single BitField <#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 <#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 <#BigBitField>, which uses an automatically managed buffer of bytes, stored in a BlobField <#BlobField>.

When bulk-updating one or more bits in a BitField <#BitField>, you can use bitwise operators to set or clear one or more bits:

    # Set the 4th bit on all Post objects.
    Post.update(flags=Post.flags | 8).execute()
    # Clear the 1st and 3rd bits on all Post objects.
    Post.update(flags=Post.flags & ~(1 | 4)).execute()

For simple operations, the flags provide handy set() and clear() methods for setting or clearing an individual bit:

    # Set the "is_deleted" bit on all posts.
    Post.update(flags=Post.is_deleted.set()).execute()
    # Clear the "is_deleted" bit on all posts.
    Post.update(flags=Post.is_deleted.clear()).execute()

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)
    # BigBitField supports item accessor by bit-number, e.g.:
    assert bitmap.data[63]
    bitmap.data[0] = 1
    del bitmap.data[0]
    # We can also combine bitmaps using bitwise operators, e.g.
    b = Bitmap(data=b'\x01')
    b.data |= b'\x02'
    assert list(b.data) == [1, 1, 0, 0, 0, 0, 0, 0]
    assert len(b.data) == 1

  Model Settings

Model-specific configuration is placed in a special Metadata <#Metadata> class called Meta:

    from peewee import *
    contacts_db = SqliteDatabase('contacts.db')
    class Person(Model):
        name = CharField()
        class Meta:
            database = contacts_db

This instructs Peewee that whenever a query is executed on Person to use the contacts database.

Once the class is defined metadata settings are accessible at ModelClass._meta:

    >>> Person.Meta
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'Person' has no attribute 'Meta'
    >>> Person._meta
    <peewee.Metadata object at 0x7f51a2f03790>

The Metadata <#Metadata> class implements several methods which may be of use for retrieving model metadata (such as lists of fields, foreign key relationships, and more).

    >>> User._meta.fields
    {'id': <peewee.AutoField object at 0x7f51a2e92750>,
     'username': <peewee.CharField object at 0x7f51a2f0a510>}
    >>> User._meta.primary_key
    <peewee.AutoField object at 0x7f51a2e92750>
    >>> User._meta.database
    <peewee.SqliteDatabase object at 0x7f519bff6dd0>

There are several options you can specify as Meta attributes. While most options are inheritable, some are table-specific and will not be inherited by subclasses.
Option | Purpose | Inheritable
database | Database instance for this model. | Yes
table_name | Explicit table name. | No
table_function | Callable that returns a table name from the class. | Yes
indexes | Tuple of multi-column index definitions. | Yes
primary_key | CompositeKey <#CompositeKey> or False. | Yes
constraints | List of table-level constraint expressions. | Yes
schema | Database schema name. | Yes
only_save_dirty | Only emit changed fields on save(). | Yes
options | Extra options for CREATE TABLE extensions. | Yes
table_settings | Strings appended after the closing parenthesis in DDL. | Yes
temporary | Mark as a temporary table. | Yes
legacy_table_names | Use legacy (non-snake-case) table name generation. | Yes
depends_on | Declare a dependency on another table for ordering. | No
without_rowid | SQLite WITHOUT ROWID tables. | No
strict_tables | SQLite strict typing (3.37+). | Yes

Example of inheritable vs non-inheritable settings:

    >>> db = SqliteDatabase(':memory:')
    >>> class ModelOne(Model):
    ...     class Meta:
    ...         database = db
    ...         table_name = 'model_one_tbl'
    ...
    >>> class ModelTwo(ModelOne):
    ...     pass
    ...
    >>> ModelOne._meta.database is ModelTwo._meta.database
    True
    >>> ModelOne._meta.table_name == ModelTwo._meta.table_name
    False

  Table naming

By default Peewee derives the table name from the model class name. The exact transformation depends on Meta.legacy_table_names:
Model class name | legacy (default) | non-legacy
User | user | user
UserProfile | userprofile | user_profile
APIResponse | apiresponse | api_response
WebHTTPRequest | webhttprequest | web_http_request

New projects should opt into non-legacy naming by setting legacy_table_names = False on BaseModel. The legacy default exists only for backwards compatibility with existing deployments.

    class BaseModel(Model):
        class Meta:
            database = db
            legacy_table_names = False   # Recommended for new projects.

To override the table name entirely, use table_name:

    class UserProfile(BaseModel):
        class Meta:
            table_name = 'acct_user_profile'   # Maps to pre-existing table.

To apply a naming convention programmatically across all models, use table_function:

    def prefixed_table_name(model_class):
        return 'myapp_' + model_class.__name__.lower()
    class BaseModel(Model):
        class Meta:
            database = db
            table_function = prefixed_table_name
    class User(BaseModel):
        pass   # Table name: "myapp_user"

  Indexes and Constraints

Peewee can create indexes on single or multiple columns, optionally including a UNIQUE constraint. Peewee also supports user-defined constraints on both models and fields.

  Single-column indexes and constraints

Single column indexes are defined by specifying index=True or unique=True when declaring the Field.

Add a unique index on username and a normal b-tree index on email:

    class User(Model):
        username = CharField(unique=True)
        email = CharField(index=True)

To add a user-defined constraint on a column, you can specify it using the constraints parameter. You may wish to specify a default value as part of the schema, or add a CHECK constraint, for example:

    class Product(Model):
        name = CharField(unique=True)
        price = DecimalField(constraints=[Check('price < 10000')])
        created = DateTimeField(constraints=[Default('CURRENT_TIMESTAMP')])

  Multi-column indexes

Multi-column indexes may be defined as Meta attributes using a nested tuple. Each database index is a 2-tuple, the first part of which is a tuple of the names of the fields, the second part a boolean indicating whether the index should be unique.

    class Transaction(Model):
        from_acct = CharField()
        to_acct = CharField()
        amount = DecimalField()
        date = DateTimeField()
        class Meta:
            indexes = (
                # create a unique on from/to/date
                (('from_acct', 'to_acct', 'date'), True),
                # create a non-unique on from/to
                (('from_acct', 'to_acct'), False),
            )

Note: Remember to add a trailing comma if your tuple of indexes contains only one item:

    class Meta:
        indexes = (
            (('first_name', 'last_name'), True),  # Note the trailing comma!
        )

  Partial and expression indexes

Partial indexes, indexes with expressions, and more complex indexes can use the Model.add_index() <#Model.add_index> API:

    class Article(BaseModel):
        name = TextField()
        timestamp = TimestampField()
        status = IntegerField()
    # Add a partial index on name and timestamp where status = 1.
    Article.add_index(Article.name, Article.timestamp,
                      where=(Article.status == 1))
    # Create a unique index on timestamp desc, status & 4.
    idx = Article.index(
        Article.timestamp.desc(),
        Article.flags.bin_and(4),
        unique=True)
    Article.add_index(idx)

Note: SQLite does not support parameterized CREATE INDEX queries. Partial indexes and expression indexes on SQLite must be written using SQL <#SQL>:

    Article.add_index(SQL('CREATE INDEX ... WHERE status = 1'))

If the above is cumbersome, you can also pass a SQL <#SQL> instance to Meta.indexes:

    class Article(BaseModel):
        name = TextField()
        timestamp = TimestampField()
        status = IntegerField()
        class Meta:
            indexes = [
                SQL('CREATE INDEX article_published_lookup ON '
                    'article (name, timestamp) WHERE status = 1'),
            ]

  Primary Keys

  Auto-incrementing integer primary key

If a model declares no primary key, Peewee automatically adds an auto-incrementing integer field named id:

    class Article(BaseModel):
        title = TextField()
        # Peewee implicitly adds: id = AutoField()

To use a different name for the auto-incrementing primary key, declare an AutoField <#AutoField> explicitly:

    class Article(BaseModel):
        article_id = AutoField()
        title = TextField()

Warning: A common mistake is writing id = IntegerField(primary_key=True) when intending an auto-incrementing primary key. This declares a plain integer column whose value the application must supply - the database will not generate it. Use AutoField <#AutoField> for auto-increment behavior.

  Non-integer primary keys

Any field can serve as the primary key by passing primary_key=True:

    class Country(BaseModel):
        code = CharField(max_length=2, primary_key=True)   # e.g. 'US', 'DE'
        name = TextField()

When using a non-auto-incrementing primary key, Peewee cannot distinguish between a new row (needs INSERT) and an existing row (needs UPDATE) by checking whether the primary key is None. On the first save, pass force_insert=True explicitly:

    country = Country(code='DE', name='Germany')
    country.save(force_insert=True)   # First save: must force INSERT.
    country.name = 'Deutschland'
    country.save()                    # Subsequent saves: UPDATE as normal.

Model.create() <#Model.create> handles this automatically, so it is the simpler option for one-step creation:

    country = Country.create(code='DE', name='Germany')

  Composite primary keys

Use CompositeKey <#CompositeKey> in Meta.primary_key to designate two or more columns as a composite primary key:

    class TweetTag(BaseModel):
        tweet = ForeignKeyField(Tweet)
        tag = TextField()
        class Meta:
            primary_key = CompositeKey('tweet', 'tag')

Composite primary keys are most appropriate for junction tables in many-to-many relationships. Peewee has limited support for foreign keys to models with composite primary keys; avoid them in models that other models will reference.

  Models without a primary key

To create a table with no primary key, set primary_key = False:

    class LogEntry(BaseModel):
        timestamp = DateTimeField()
        event = TextField()
        class Meta:
            primary_key = False

Note that Model.save() <#Model.save> and Model.delete_instance() <#Model.delete_instance> do not work on keyless models, since both require a primary key to target a specific row. Use Model.insert() <#Model.insert>, Model.update() <#Model.update>, and Model.delete() <#Model.delete> (the class-level query methods) instead.

  Table Constraints

Peewee allows arbitrary constraints to Model <#Model> classes.

Suppose you have a people table with a composite primary key of two columns: the person's first and last name. You wish to have another table relate to the people table. To do this define a multi-column foreign key constraint:

    class Person(Model):
        first = CharField()
        last = CharField()
        class Meta:
            primary_key = CompositeKey('first', 'last')
    class Pet(Model):
        owner_first = CharField()
        owner_last = CharField()
        pet_name = CharField()
        class Meta:
            constraints = [SQL('FOREIGN KEY(owner_first, owner_last) '
                               'REFERENCES person(first, last)')]

CHECK constraints can be specified at the table level:

    class Product(Model):
        name = CharField(unique=True)
        price = DecimalField()
        class Meta:
            constraints = [Check('price < 10000')]

  Creating Tables

Once models are defined, create their corresponding tables with Database.create_tables() <#Database.create_tables>:

    db.create_tables([User, Tweet, Favorite])

To create a single table, use Model.create_table() <#Model.create_table>:

    Tweet.create_table()

See also: Schema Management <#schema> for documentation on table creation and other schema management tasks.

  Advanced Topics

The following sections cover scenarios that arise less frequently. New users can skip this section and return to it when the need arises.

  Circular foreign key dependencies

Sometimes it happens that you will create a circular dependency between two tables.

Note: Circular foreign keys should be refactored (by adding an intermediary table, for instance).

Adding circular foreign keys with peewee is a bit tricky because at the time you are defining either foreign key, the model it points to will not have been defined yet, causing a NameError.

    class User(Model):
        username = CharField()
        favorite_tweet = ForeignKeyField(Tweet, null=True)  # NameError!!
    class Tweet(Model):
        message = TextField()
        user = ForeignKeyField(User, backref='tweets')

One option is to simply use an IntegerField <#IntegerField> to store the raw ID:

    class User(Model):
        username = CharField()
        favorite_tweet_id = IntegerField(null=True)

By using DeferredForeignKey <#DeferredForeignKey> we can get around the problem and still use a foreign key field:

    class User(BaseModel):
        username = TextField()
        favorite_tweet = DeferredForeignKey('Tweet', null=True)
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
    db.create_tables([User, Tweet])
    # Add the constraint that could not be created at table-creation time.
    User._schema.create_foreign_key(User.favorite_tweet)

Note: Because SQLite has limited support for altering tables, foreign-key constraints cannot be added to a table after it has been created.

  Field naming conflicts

Several names are reserved by Model <#Model> for built-in methods and attributes (for example save, create, delete, update, get). Declaring a field with one of these names overwrites the method.

When the desired column name conflicts with a model method, supply an alternative attribute name and set column_name explicitly:

    class LogEntry(BaseModel):
        timestamp = DateTimeField()
        # "create" and "update" would conflict with Model.create / Model.update.
        created_at = DateTimeField(column_name='create')
        updated_at = DateTimeField(column_name='update')

The database column is still named create and update; the Python attributes are created_at and updated_at.

  BareField (SQLite only)

BareField <#BareField> declares a column with no type affinity. It is only meaningful with SQLite, which permits untyped columns and virtual table columns.

    class FTSEntry(BaseModel):
        content = BareField()

The optional adapt parameter specifies a callable that converts values coming from the database into a Python type:

    class RawData(BaseModel):
        value = BareField(adapt=float)

For full-text search virtual tables, use SearchField rather than BareField <#BareField>. See Full-Text Search <#sqlite-fts>.

  Custom fields

A custom field is a subclass of an existing field that overrides the Python-to-database and database-to-Python conversion methods. This is most useful when a database offers a column type that has no built-in Peewee equivalent, or when a standard column type should carry application-specific Python behavior.

The two conversion hooks are:

•   db_value(self, value) - converts a Python value to the format the database driver expects.

•   python_value(self, value) - converts a value from the database driver into the desired Python type.

    The following example implements a field that stores a pathlib.Path value as a TEXT column:

        from pathlib import Path
        class PathField(TextField):
            def db_value(self, value):
                return str(value) if value is not None else None
            def python_value(self, value):
                return Path(value) if value is not None else None
        class Document(BaseModel):
            path = PathField()
        doc = Document.create(path=Path('/var/data/report.pdf'))
        assert isinstance(doc.path, Path)

    When the database requires a completely new storage type (not a variant of an existing one), set field_type to the type label and register the label with each database that will use it:

        class PointField(Field):
            field_type = 'point'   # Custom type label.
            def db_value(self, value):
                if value is not None:
                    return f'{value[0]},{value[1]}'
            def python_value(self, value):
                if value is not None:
                    x, y = value.split(',')
                    return (float(x), float(y))
        # Tell Peewee what DDL type to emit for each database.
        sq_db  = SqliteDatabase('mydb', field_types={'point': 'text'})

    See also: Field <#Field> API reference.

  Relationships and Joins

Relational databases derive most of their power from the ability to link rows across tables. This document explains how Peewee models those links, what happens under the hood when you traverse them, and how to write queries that cross table boundaries efficiently.

By the end of this document you will understand:

•   How ForeignKeyField <#ForeignKeyField> behaves at runtime, not just at schema definition time.

•   What a back-reference is and when to use one.

•   What the N+1 problem is and how to recognise it.

•   How to write joins, including multi-table and self-referential joins.

•   How many-to-many relationships are modelled.

•   When to use prefetch() <#prefetch> instead of a join.

  Model Definitions

All examples in this document use the following three models. They will be defined once here and reused throughout.

    import datetime
    from peewee import *
    db = SqliteDatabase(':memory:')
    class BaseModel(Model):
        class Meta:
            database = db
    class User(BaseModel):
        username = TextField()
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(default=datetime.datetime.now)
    class Favorite(BaseModel):
        user = ForeignKeyField(User, backref='favorites')
        tweet = ForeignKeyField(Tweet, backref='favorites')

A ForeignKeyField <#ForeignKeyField> links one model to another. Tweet.user links each tweet to the user who wrote it. Favorite.user and Favorite.tweet together record which users have favorited which tweets.

The following helper populates test data that the examples below will query:

    def create_test_data():
        db.create_tables([User, Tweet, Favorite])
        users = {
            name: User.create(username=name)
            for name in ('huey', 'mickey', 'zaizee')
        }
        tweet_data = {
            'huey':   ('meow', 'hiss', 'purr'),
            'mickey': ('woof', 'whine'),
            'zaizee': (),
        }
        tweets = {}
        for username, contents in tweet_data.items():
            for content in contents:
                tweets[content] = Tweet.create(
                    user=users[username],
                    content=content)
        # huey favorites mickey's "whine",
        # mickey favorites huey's "purr",
        # zaizee favorites huey's "meow" and "purr".
        favorite_data = (
            ('huey',   ['whine']),
            ('mickey', ['purr']),
            ('zaizee', ['meow', 'purr']),
        )
        for username, contents in favorite_data:
            for content in contents:
                Favorite.create(user=users[username], tweet=tweets[content])

This gives the following data:
User | Tweet | Favorited by
huey | meow | zaizee
huey | hiss |
huey | purr | mickey, zaizee
mickey | woof |
mickey | whine | huey
zaizee | (no tweets) |

Note: To log every query Peewee executes to the console - useful for verifying query counts while working through this document - add the following before running any queries:

    import logging
    logging.getLogger('peewee').addHandler(logging.StreamHandler())
    logging.getLogger('peewee').setLevel(logging.DEBUG)

  Foreign Keys

When you declare a ForeignKeyField <#ForeignKeyField>, Peewee creates two things on the model: a field that stores the raw integer ID value, and a descriptor that resolves that ID into a full model instance on access.

    tweet = Tweet.get(Tweet.content == 'meow')
    # Accessing .user resolves the foreign key - Peewee issues a SELECT
    # query to fetch the related User row.
    print(tweet.user.username)  # 'huey'
    # Accessing .user_id returns the raw integer stored in the column,
    # without issuing any query.
    print(tweet.user_id)  # 1

The _id suffix accessor is available for every foreign key field. Use it whenever only the ID value is needed, since it avoids the extra query entirely.

  Lazy loading

By default, a ForeignKeyField <#ForeignKeyField> is lazy-loaded: the related object is not fetched until the attribute is first accessed, at which point a SELECT query is issued automatically. This is convenient but can lead to performance problems - see The N+1 Problem below.

To disable lazy loading on a specific field, pass lazy_load=False. With lazy loading disabled, accessing the attribute returns the raw ID value rather than issuing a query, matching the behaviour of the _id accessor:

    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets', lazy_load=False)
    for tweet in Tweet.select():
        # Returns the integer ID, not a User instance. No extra query.
        print(tweet.user)
    # If the User data was eagerly loaded via a join, the full User
    # instance is accessible as normal, even with lazy_load=False.
    for tweet in Tweet.select(Tweet, User).join(User):
        print(tweet.user.username)

See also: The N+1 Problem explains when and why disabling lazy loading is useful.

  Back-references

Every ForeignKeyField <#ForeignKeyField> automatically creates a back-reference on the related model. The back-reference is a pre-filtered Select <#Select> query that returns all rows pointing at a given instance.

In the example schema, Tweet.user is a foreign key to User. The backref='tweets' parameter means that every User instance gains a tweets attribute, which is a pre-filtered Select <#Select> query:

    >>> huey = User.get(User.username == 'huey')
    >>> huey.tweets  # back-reference is a Select query.
    <peewee.ModelSelect object at 0x...>
    >>> for tweet in huey.tweets:
    ...     print(tweet.content)
    meow
    hiss
    purr

Taking a closer look at huey.tweets, we can see that it is just a simple pre-filtered SELECT query:

    >>> huey.tweets
    <peewee.ModelSelect at 0x7f0483931fd0>
    >>> huey.tweets.sql()
    ('SELECT "t1"."id", "t1"."content", "t1"."timestamp", "t1"."user_id"
      FROM "tweet" AS "t1" WHERE ("t1"."user_id" = ?)', [1])

A back-reference behaves like any other Select <#Select> query and can be filtered, ordered, and chained:

    recent = (huey.tweets
              .order_by(Tweet.timestamp.desc())
              .limit(2))

If no backref name is specified, Peewee generates one automatically using the pattern <lowercase_classname>_set. Specifying an explicit backref is recommended for clarity.

  The N+1 Problem

The N+1 problem occurs when code issues one query to fetch a list of N rows, then issues one or more additional queries per row to fetch related data - N+1 queries in total instead of one or two. At small scale this is invisible, but at production scale it can make pages that should take milliseconds take seconds.

Consider printing every tweet alongside its author's username:

    # Bad: issues 1 query for tweets + 1 query per tweet for the user.
    for tweet in Tweet.select():
        print(tweet.user.username, '->', tweet.content)
    # Good: only one query is needed.
    query = (Tweet
             .select(Tweet, User)
             .join(User))
    for tweet in query:
        # tweet.user is a User instance populated from the joined data.
        # No additional query is issued.
        print(tweet.user.username, '->', tweet.content)

Without joining and selecting the related User, each access to tweet.user triggers a SELECT on the user table. With five tweets, this produces six queries. With five thousand tweets, it produces five thousand and one.

The same problem can occur when iterating over back-references:

    # Bad: issues 1 query for users + 1 query per user for their tweets.
    for user in User.select():
        print(user.username)
        for tweet in user.tweets:  # A new query for each user.
            print('  ', tweet.content)
    # Better:
    for user in User.select().prefetch(Tweet):
        print(user.username)
        for tweet in user.tweets:  # Pre-fetched, no additional query.
            print('  ', tweet.content)

Peewee provides two complementary tools for avoiding N+1 queries:

•   Joins - combine rows from multiple tables in a single SELECT. Best when traversing a foreign key toward its target (many-to-one direction), for example fetching tweets with their authors.

•   Prefetch - issue one query per table and stitch the results together in Python. Best when traversing a back-reference (one-to-many direction), for example fetching users with all their tweets.

    Both are covered in the sections below. The choice between them depends on the shape of the query.

  Joins

A SQL join combines columns from two or more tables into a single result set. Peewee's join() <#ModelSelect.join> method generates the appropriate JOIN clause and, when the full result is returned as model instances, reconstructs the model graph automatically.

  Join context

Peewee tracks a join context: the model from which the next join() call will depart. At the start of a query the join context is the model being selected from. Each call to join() moves the join context to the model just joined.

    # Context starts at Tweet.
    # After .join(User), context moves to User.
    query = Tweet.select().join(User)

When joining through multiple tables in a chain, this is usually what you want. When joining from one model to two different models, the join context needs to be reset explicitly using switch() <#ModelSelect.switch> or join_from() <#ModelSelect.join_from>.

Peewee infers the join predicate (the ON clause) from the foreign keys defined on the models. If only one foreign key exists between two models, no additional specification is required. If multiple foreign keys exist, the relevant one must be specified explicitly.

The following code is equivalent to the prevoius example:

    query = (Tweet
             .select()
             .join(User, on=(Tweet.user == User.id))
             .where(User.username == 'huey'))

  Simple joins

To fetch all of huey's tweets, join from Tweet to User and filter on the username:

    query = (Tweet
             .select()
             .join(User)
             .where(User.username == 'huey'))
    for tweet in query:
        print(tweet.content)

Peewee inferred the join predicate since Tweet.user is the only key between the two models. To explicitly specify the join predicate use on=:

    query = (Tweet
             .select()
             .join(User, on=(Tweet.user == User.id))
             .where(User.username == 'huey'))

If a User instance is already available, the back-reference is simpler and equivalent for straightforward cases:

    huey = User.get(User.username == 'huey')
    for tweet in huey.tweets:
        print(tweet.content)

The join is the better choice when filtering or joining further. The back-reference is more readable for simple access to related rows.

  Joining across multiple tables

To count how many favorites each user has received across all their tweets, a join must traverse User -> Tweet -> Favorite. Because each join moves the context forward, this chain can be written directly:

    # Context: User -> join -> Tweet -> join -> Favorite
    query = (User
             .select(User.username, fn.COUNT(Favorite.id).alias('fav_count'))
             .join(Tweet, JOIN.LEFT_OUTER)
             .join(Favorite, JOIN.LEFT_OUTER)
             .group_by(User.username))
    for user in query:
        print(f'{user.username}: {user.fav_count} favorites received')

Both joins use LEFT OUTER because a user may have no tweets, and a tweet may have no favorites - yet both should appear in the result with a count of zero.

  Switching join context

When a query needs to branch - joining from one model to two different models - the join context must be reset manually using switch() <#ModelSelect.switch>.

To find all tweets by huey and how many times each has been favorited:

    # Context: Tweet -> join -> User (context is now User)
    # switch(Tweet) resets context to Tweet
    # -> join -> Favorite (context is now Favorite)
    query = (Tweet
             .select(Tweet.content, fn.COUNT(Favorite.id).alias('fav_count'))
             .join(User)
             .switch(Tweet)
             .join(Favorite, JOIN.LEFT_OUTER)
             .where(User.username == 'huey')
             .group_by(Tweet.content))
    for tweet in query:
        print(f'{tweet.content}: favorited {tweet.fav_count} times')

Without the call to .switch(Tweet), Peewee would attempt to join from User to Favorite using Favorite.user, which would produce incorrect results.

  Using join_from

join_from() <#ModelSelect.join_from> is an alternative to switch().join() that makes the join source explicit in a single call. The above query can be written equivalently as:

    query = (Tweet
             .select(Tweet.content, fn.COUNT(Favorite.id).alias('fav_count'))
             .join_from(Tweet, User)
             .join_from(Tweet, Favorite, JOIN.LEFT_OUTER)
             .where(User.username == 'huey')
             .group_by(Tweet.content))

join_from(A, B) is equivalent to switch(A).join(B) and is often more readable when a query branches across several paths.

  Selecting columns from joined models

When columns from multiple models are included in select(), Peewee reconstructs the model graph and assigns related model instances to their corresponding attributes.

    query = (Tweet
             .select(Tweet.content, User.username)
             .join(User))
    for tweet in query:
        # tweet.user is a User instance populated from the joined data.
        # No additional query is issued.
        print(tweet.user.username, '->', tweet.content)
    # huey -> meow
    # huey -> hiss
    # huey -> purr
    # mickey -> woof
    # mickey -> whine

To make it a bit more obvious that it's doing the correct thing, we can ask Peewee to return the rows as dictionaries.

    query = (Tweet
             .select(Tweet.content, User.username)
             .join(User)
             .dicts())
    for row in query:
        print(row)
    # {'content': 'meow', 'username': 'huey'}
    # {'content': 'hiss', 'username': 'huey'}
    # {'content': 'purr', 'username': 'huey'}
    # {'content': 'woof', 'username': 'mickey'}
    # {'content': 'whine', 'username': 'mickey'}

Compare these queries to the N+1 version: here, only one query is executed regardless of how many tweets are returned.

The attribute name that Peewee uses to store the joined instance follows the foreign key field name (tweet.user in this case). To override it, pass attr to join():

    query = (Tweet
             .select(Tweet.content, User.username)
             .join(User, attr='author'))
    for tweet in query:
        print(tweet.author.username, '->', tweet.content)

To flatten all selected columns onto the primary model instance rather than nesting them in a sub-object, append .objects():

    query = (Tweet
             .select(Tweet.content, User.username)
             .join(User)
             .objects())
    for tweet in query:
        # username is now an attribute on tweet directly.
        print(tweet.username, '->', tweet.content)
    # huey -> meow

See Row Types <#row-types> for the different ways Peewee can return rows.

  More complex example

As a more complex example, in this query, we will write a single query that selects all the favorites, along with the user who created the favorite, the tweet that was favorited, and that tweet's author.

In SQL we would write:

    SELECT owner.username, tweet.content, author.username AS author
    FROM favorite
    INNER JOIN user AS owner ON (favorite.user_id = owner.id)
    INNER JOIN tweet ON (favorite.tweet_id = tweet.id)
    INNER JOIN user AS author ON (tweet.user_id = author.id);

Note that we are selecting from the user table twice - once in the context of the user who created the favorite, and again as the author of the tweet.

With Peewee, we use Model.alias() <#Model.alias> to alias a model class so it can be referenced twice in a single query:

    Owner = User.alias()
    query = (Favorite
             .select(Favorite, Tweet.content, User.username, Owner.username)
             .join_from(Favorite, Owner)  # Determine owner of favorite.
             .join_from(Favorite, Tweet)  # Join favorite -> tweet.
             .join_from(Tweet, User))     # Join tweet -> user.

We can iterate over the results and access the joined values in the following way. Note how Peewee has resolved the fields from the various models we selected and reconstructed the model graph:

    for fav in query:
        print(fav.user.username, 'liked', fav.tweet.content, 'by', fav.tweet.user.username)
    # huey liked whine by mickey
    # mickey liked purr by huey
    # zaizee liked meow by huey
    # zaizee liked purr by huey

  Subqueries

Peewee allows you to join on any table-like object, including subqueries or common table expressions (see Common Table Expressions <#cte>). To demonstrate joining on a subquery, let's query for all users and their latest tweet.

Here is the SQL:

    SELECT tweet.*, user.*
    FROM tweet
    INNER JOIN (
        SELECT latest.user_id, MAX(latest.timestamp) AS max_ts
        FROM tweet AS latest
        GROUP BY latest.user_id) AS latest_query
    ON ((tweet.user_id = latest_query.user_id) AND (tweet.timestamp = latest_query.max_ts))
    INNER JOIN user ON (tweet.user_id = user.id)

We'll do this by creating a subquery which selects each user and the timestamp of their latest tweet. Then we can query the tweets table in the outer query and join on the user and timestamp combination from the subquery.

    # Define our subquery first. We'll use an alias of the Tweet model, since
    # we will be querying from the Tweet model directly in the outer query.
    Latest = Tweet.alias()
    latest_query = (Latest
                    .select(Latest.user, fn.MAX(Latest.timestamp).alias('max_ts'))
                    .group_by(Latest.user)
                    .alias('latest_query'))
    # Our join predicate will ensure that we match tweets based on their
    # timestamp *and* user_id.
    predicate = ((Tweet.user == latest_query.c.user_id) &
                 (Tweet.timestamp == latest_query.c.max_ts))
    # We put it all together, querying from tweet and joining on the subquery
    # using the above predicate.
    query = (Tweet
             .select(Tweet, User)  # Select all columns from tweet and user.
             .join_from(Tweet, latest_query, on=predicate)  # Join tweet -> subquery.
             .join_from(Tweet, User))  # Join from tweet -> user.

Iterating over the query, we can see each user and their latest tweet.

    for tweet in query:
        print(tweet.user.username, '->', tweet.content)
    # huey -> purr
    # mickey -> whine

There are a couple things you may not have seen before in the code we used to create the query in this section:

•   We used join_from() <#ModelSelect.join_from> to explicitly specify the join context. We wrote .join_from(Tweet, User), which is equivalent to .switch(Tweet).join(User).

•   We referenced columns in the subquery using the magic .c attribute, for example latest_query.c.max_ts. The .c attribute is used to dynamically create column references.

•   Instead of passing individual fields to Tweet.select(), we passed the Tweet and User models. This is shorthand for selecting all fields on the given model.

  Common-table Expressions

In the previous section we joined on a subquery, but we could just as easily have used a common-table expression (CTE) <#cte>. We will repeat the same query as before, listing users and their latest tweets, but this time we will do it using a CTE.

Here is the SQL:

    WITH latest AS (
        SELECT user_id, MAX(timestamp) AS max_ts
        FROM tweet
        GROUP BY user_id)
    SELECT tweet.*, user.*
    FROM tweet
    INNER JOIN latest
        ON ((latest.user_id = tweet.user_id) AND (latest.max_ts = tweet.timestamp))
    INNER JOIN user
        ON (tweet.user_id = user.id)

This example looks very similar to the previous example with the subquery:

    # Define our CTE first. We'll use an alias of the Tweet model, since
    # we will be querying from the Tweet model directly in the main query.
    Latest = Tweet.alias()
    cte = (Latest
           .select(Latest.user, fn.MAX(Latest.timestamp).alias('max_ts'))
           .group_by(Latest.user)
           .cte('latest'))
    # Our join predicate will ensure that we match tweets based on their
    # timestamp *and* user_id.
    predicate = ((Tweet.user == cte.c.user_id) &
                 (Tweet.timestamp == cte.c.max_ts))
    # We put it all together, querying from tweet and joining on the CTE
    # using the above predicate.
    query = (Tweet
             .select(Tweet, User)  # Select all columns from tweet and user.
             .join(cte, on=predicate)  # Join tweet -> CTE.
             .join_from(Tweet, User)  # Join from tweet -> user.
             .with_cte(cte))

We can iterate over the result-set, which consists of the latest tweets for each user:

    for tweet in query:
        print(tweet.user.username, '->', tweet.content)
    # huey -> purr
    # mickey -> whine

Note: For more information about using CTEs, including information on writing recursive CTEs, see the Common Table Expressions <#cte> section of the "Querying" document.

  Multiple foreign keys to the same model

When two foreign keys on the same model both point at the same target model, Peewee cannot infer which one to use for a join. The field must be specified explicitly.

Consider a Relationship model recording which users follow which other users:

    class Relationship(BaseModel):
        from_user = ForeignKeyField(User, backref='following')
        to_user = ForeignKeyField(User, backref='followers')
        class Meta:
            indexes = ((('from_user', 'to_user'), True),)

To find everyone that huey follows:

    huey = User.get(User.username == 'huey')
    following = (User
                 .select()
                 .join(Relationship, on=Relationship.to_user)
                 .where(Relationship.from_user == huey))

To find everyone who follows huey:

    followers = (User
                 .select()
                 .join(Relationship, on=Relationship.from_user)
                 .where(Relationship.to_user == huey))

Passing the field instance to on= tells Peewee which foreign key column to use for the join.

  Joining without a foreign key

A join can be performed on any two tables, even when no ForeignKeyField <#ForeignKeyField> exists between them, by supplying an explicit join predicate as an expression:

    query = (User
             .select(User, ActivityLog)
             .join(ActivityLog,
                   on=(User.id == ActivityLog.object_id),
                   attr='log')
             .where(
                 (ActivityLog.activity_type == 'login') &
                 (User.username == 'huey')))
    for user in query:
        print(user.username, '->', user.log.description)

  Self-joins

A self-join queries a model against an alias of itself. Use Model.alias() <#Model.alias> to create the alias:

    # Find all categories and their immediate parent name.
    class Category(BaseModel):
        name = TextField()
        parent = ForeignKeyField('self', null=True, backref='children')
    Parent = Category.alias()
    query = (Category
             .select(Category.name, Parent.name)
             .join(Parent, JOIN.LEFT_OUTER, on=(Category.parent == Parent.id))
             .order_by(Category.name))
    for row in query:
        print(row.name, 'parent:', row.parent.name if row.parent else 'None')

See also: Recursive queries over self-referential structures are covered in Common Table Expressions <#cte> using recursive CTEs.

  Many-to-Many Relationships

A many-to-many relationship - where one row in table A can relate to many rows in table B and vice versa - requires an intermediate through table that holds pairs of foreign keys.

  Manual through table (recommended)

The explicit approach gives full control over the through table and its queries:

    class Student(BaseModel):
        name = TextField()
    class Course(BaseModel):
        title = TextField()
    class Enrollment(BaseModel):
        """Through table linking students to courses."""
        student = ForeignKeyField(Student, backref='enrollments')
        course = ForeignKeyField(Course, backref='enrollments')
        enrolled_on = DateField(default=datetime.date.today)
        class Meta:
            indexes = (
                (('student', 'course'), True),
            )

To query all courses a given student is enrolled in:

    huey = Student.get(Student.name == 'Huey')
    courses = (Course
               .select()
               .join(Enrollment)
               .where(Enrollment.student == huey)
               .order_by(Course.title))
    for course in courses:
        print(course.title)

To query all students in a given course, along with when they enrolled:

    cs101 = Course.get(Course.title == 'CS 101')
    query = (Student
             .select(Student, Enrollment.enrolled_on)
             .join(Enrollment)
             .where(Enrollment.course == cs101)
             .order_by(Student.name))
    for student in query:
        print(student.name, student.enrollment.enrolled_on)
    # To attach enrollment date to the Student for simplicity:
    for student in query.objects():
        print(student.name, student.enrolled_on)

Since all data is available via the through table model, this approach is most flexible and handles any querying requirement without special casing.

  ManyToManyField

ManyToManyField <#ManyToManyField> provides a shortcut API that manages the through table automatically. It is suitable for simple cases where the through table requires no extra columns and complex querying is not needed.

    class Student(BaseModel):
        name = TextField()
    class Course(BaseModel):
        title = TextField()
        students = ManyToManyField(Student, backref='courses')
    # Retrieve the auto-generated through model if direct access is needed.
    Enrollment = Course.students.get_through_model()
    db.create_tables([Student, Course, Enrollment])
    huey = Student.create(name='Huey')
    cs101 = Course.create(title='CS 101')
    # Adding and removing relationships:
    huey.courses.add(cs101)
    huey.courses.add(Course.select().where(Course.title.contains('Math')))
    cs101.students.remove(huey)
    cs101.students.clear()   # Removes all students from this course.
    # Querying through the field:
    for course in huey.courses.order_by(Course.title):
        print(course.title)

Warning: ManyToManyField <#ManyToManyField> does not work correctly with model inheritance. The through table contains foreign keys back to the original models, and those pointers are not automatically updated for subclasses. For any model that will be subclassed, use an explicit through table instead.

See also: ManyToManyField.add() <#ManyToManyField.add>, ManyToManyField.remove() <#ManyToManyField.remove>, ManyToManyField.clear() <#ManyToManyField.clear>, ManyToManyField.get_through_model() <#ManyToManyField.get_through_model>.

  Avoiding N+1 with Prefetch

Joins solve the N+1 problem when traversing from the many side toward the one side - for example, fetching tweets with their authors. Each tweet has exactly one author, so a join produces exactly one result row per tweet.

The situation is different when traversing from the one side toward the many side - for example, fetching users with all their tweets. A join in this direction produces one result row per tweet, which means users with multiple tweets appear multiple times in the result set. Deduplicating those rows in application code is awkward and error-prone.

prefetch() <#prefetch> solves this by issuing one query per table, then stitching the results together in Python. Instead of O(n) queries for n rows, we will do O(k) queries for k tables:

    # Two queries total, regardless of how many users or tweets there are:
    # SELECT * FROM user
    # SELECT * FROM tweet WHERE user_id IN (...)
    users = User.select().prefetch(Tweet)
    # Equivalent to above.
    users = prefetch(User.select(), Tweet.select())
    for user in users:
        print(user.username)
        for tweet in user.tweets:  # No additional query, user.tweets is a list.
            print(f'  {tweet.content}')

The models passed to prefetch() <#prefetch> must be linked by foreign keys. Peewee infers the relationships and assigns the prefetched rows to the appropriate back-reference attribute on each instance.

Prefetch can span more than two tables. To fetch users, their tweets, and the favorites on each tweet in three queries:

    users = prefetch(User.select(), Tweet.select(), Favorite.select())
    for user in users:
        for tweet in user.tweets:
            print(f'{user.username}: {tweet.content} '
                  f'({len(tweet.favorites)} favorites)')

  Filtering prefetched rows

Both the outer query and the prefetch subqueries can carry WHERE clauses and other modifiers independently:

    one_week_ago = datetime.date.today() - datetime.timedelta(days=7)
    users = prefetch(
        User.select().order_by(User.username),
        Tweet.select().where(Tweet.timestamp >= one_week_ago),
    )

The filter on Tweet applies only to the prefetched tweets; it does not affect which users are returned.

  Choosing between joins and prefetch

Use a join when:

•   Traversing from the many side to the one side (tweet -> author).

•   Filtering on columns in the related table (tweets by users whose username starts with "h").

•   Only a subset of related fields is needed.

    Use prefetch when:

•   Traversing from the one side to the many side (user -> all their tweets).

•   The full set of related rows is needed for each parent row.

•   Nesting more than one level of related data (users -> tweets -> favorites).

    Note: LIMIT on the outer query of a prefetch() <#prefetch> call works as expected. Limiting the inner queries (the prefetched tables) is not directly supported and requires a manual approach - see Top N Per Group <#top-n-per-group> in the recipes document for techniques.

    See also: prefetch() <#prefetch> API reference.

  Querying

This document covers reading data from the database: SELECT queries, filtering, sorting, aggregation, and result-set iteration. Writing data (INSERT, UPDATE, DELETE) is covered in Writing Data <#writing>.

All examples use the following models (see Models and Fields <#models>):

    import datetime
    from peewee import *
    db = SqliteDatabase(':memory:')
    class BaseModel(Model):
        class Meta:
            database = db
    class User(BaseModel):
        username = TextField(unique=True)
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(default=datetime.datetime.now)
        is_published = BooleanField(default=True)

See also: Extensive library of SQL / Peewee examples <#query-library>

  Selecting Records

Model.select() <#Model.select> returns a Select <#Select> query. The query is lazy - the database is not queried until you iterate over the result, index, slice or call a method that forces execution.

    # All users. No query issued yet.
    query = User.select()
    # Query executes here.
    for user in query:
        print(user.username)

Iterating over the same query object a second time does not re-query the database: results are cached on the query object. To disable caching (for example, when iterating over a large result set), use iterator() <#BaseQuery.iterator>:

    for user in User.select().iterator():
        process(user)   # One row at a time, not cached.

To select specific columns rather than all columns, pass field expressions to select():

    for user in User.select(User.username):
        print(user.username)
        # user.id is not populated - it was not selected.

To select columns from multiple models, pass both model classes or their fields. Peewee reconstructs the model graph from the result set:

    query = Tweet.select(Tweet, User).join(User)
    for tweet in query:
        # tweet.user is a fully populated User instance.
        # No extra query is issued.
        print(tweet.user.username, '->', tweet.content)

See also: Relationships and Joins <#relationships> covers joins in detail.

  Retrieving a Single Record

Model.get() <#Model.get> executes the query and returns the first matching row. If no row matches, DoesNotExist is raised:

    user = User.get(User.username == 'charlie')
    # Equivalent long form:
    user = User.select().where(User.username == 'charlie').get()

get_by_id() <#Model.get_by_id> and the subscript operator are shortcuts for primary-key lookups:

    user = User.get_by_id(1)
    user = User[1]             # Same.

get_or_none() <#Model.get_or_none> returns None instead of raising an exception when no row is found:

    user = User.get_or_none(User.username == 'charlie')
    if user is None:
        print('Not found.')

first() <#SelectBase.first> returns the first row of a query, or None:

    latest = Tweet.select().order_by(Tweet.timestamp.desc()).first()

  Get or Create

get_or_create() <#Model.get_or_create> retrieves a matching row, or creates it if it does not exist. It returns a (instance, created) tuple:

    user, created = User.get_or_create(username='charlie')
    if created:
        print('New user created.')

Use the defaults keyword to supply values that are only used during creation, not as lookup keys:

    user, created = User.get_or_create(
        username='charlie',
        defaults={'joined': datetime.date.today()})

When uniqueness is enforced by a database constraint, the recommended pattern is to attempt creation first and fall back to retrieval on failure:

    try:
        with db.atomic():
            return User.create(username=username)
    except IntegrityError:
        return User.get(User.username == username)

This avoids a race window between the lookup and the insert.

  Filtering

where() <#Query.where> accepts expressions built from field comparisons. Peewee overloads Python's comparison operators to produce SQL expressions:

    # Equality
    User.select().where(User.username == 'charlie')
    # Inequality
    Tweet.select().where(Tweet.is_published != False)
    # Comparison
    Tweet.select().where(Tweet.timestamp < datetime.datetime(2024, 1, 1))

Warning: Peewee uses bitwise operators (& and |) rather than logical operators (and and or). The reason for this is that Python coerces the logical operations to a boolean value. This is also the reason why "IN" queries must be expressed using .in_() rather than the in operator.

See also: Query operations <#query-operators> to see all operators.

Combine conditions with & (AND) and | (OR):

    # Published tweets by charlie:
    query = (Tweet
             .select()
             .join(User)
             .where(
                 (User.username == 'charlie') &
                 (Tweet.is_published == True)))
    # Tweets by charlie OR huey:
    query = (Tweet
             .select()
             .join(User)
             .where(
                 (User.username == 'charlie') |
                 (User.username == 'huey')))

Negate a condition with ~:

    # All users except charlie:
    User.select().where(~(User.username == 'charlie'))

Calling .where() multiple times on a query ANDs the conditions:

    # Equivalent to WHERE is_published = 1 AND timestamp > ...
    query = (Tweet
             .select()
             .where(Tweet.is_published == True)
             .where(Tweet.timestamp > one_week_ago))

  Common filtering methods
Method | SQL equivalent
User.username == 'charlie' | username = 'charlie'
User.username != 'charlie' | username != 'charlie'
Tweet.timestamp < dt | timestamp < dt
Tweet.timestamp >= dt | timestamp >= dt
Tweet.timestamp.between(start, end) | timestamp BETWEEN start AND end
User.username.in_(['a', 'b']) | username IN ('a', 'b')
User.username.not_in(['a', 'b']) | username NOT IN ...
User.username.contains('char') | username LIKE '%char%'
User.username.startswith('ch') | username LIKE 'ch%'
User.username.endswith('ie') | username LIKE '%ie'
User.username.regexp(r'^[a-z]+$') | username REGEXP ...
User.username.is_null() | username IS NULL
User.username.is_null(False) | username IS NOT NULL

Note: IN queries must use .in_() rather than Python's in operator, because Python's in returns a boolean and cannot be overridden.

See also: Query Operators <#query-operators> for the full list of supported operators and methods.

  SQL functions

The fn <#fn> helper calls any SQL function by name:

    from peewee import fn
    # Users whose username starts with a vowel (case-insensitive).
    vowels = ('a', 'e', 'i', 'o', 'u')
    query = User.select().where(
        fn.LOWER(fn.SUBSTR(User.username, 1, 1)).in_(vowels))
    # Tweets whose content is less than 10 characters long.
    query = Tweet.select().where(
        fn.LENGTH(Tweet.content) < 10)

  Sorting

order_by() <#Query.order_by> specifies the column(s) to sort by:

    # Ascending (default).
    Tweet.select().order_by(Tweet.timestamp)
    # Descending.
    Tweet.select().order_by(Tweet.timestamp.desc())
    # Using the + / - prefix operators:
    Tweet.select().order_by(+Tweet.timestamp)     # Ascending.
    Tweet.select().order_by(-Tweet.timestamp)     # Descending.

Sort on multiple columns by passing multiple arguments:

    query = (Tweet
             .select()
             .join(User)
             .order_by(User.username, Tweet.timestamp.desc()))

  Sorting by a calculated or aliased value

When ordering by an aggregate or expression that appears in select(), reference it by re-using the expression or by wrapping the alias in SQL <#SQL>:

    tweet_count = fn.COUNT(Tweet.id)
    query = (User
             .select(User.username, tweet_count.alias('num_tweets'))
             .join(Tweet, JOIN.LEFT_OUTER)
             .group_by(User.username)
             .order_by(tweet_count.desc()))
    # Alternatively, reference the alias string via SQL():
    query = (User
             .select(User.username, fn.COUNT(Tweet.id).alias('num_tweets'))
             .join(Tweet, JOIN.LEFT_OUTER)
             .group_by(User.username)
             .order_by(SQL('num_tweets').desc()))

  Random ordering

Database.random() <#Database.random> provides a database-specific implementation of the random function, which can be used for ordering:

    # Select 5 random winners.
    LotteryEntry.select().order_by(db.random()).limit(5)

  Pagination, Limiting, and Offsetting

limit() <#Query.limit> and offset() <#Query.offset> map directly to SQL:

    # First 10 rows.
    Tweet.select().order_by(Tweet.id).limit(10)
    # Rows 11-20.
    Tweet.select().order_by(Tweet.id).limit(10).offset(10)

paginate() <#Query.paginate> is a convenience wrapper:

    # Page 3, 20 items per page (rows 41-60).
    Tweet.select().order_by(Tweet.id).paginate(3, 20)

Attention: Page numbers are 1-based. Page 1 returns the first items_per_page rows.

  Counting

count() <#SelectBase.count> wraps the query in a SELECT COUNT(1) FROM (...) and returns an integer:

    total = Tweet.select().count()
    published = Tweet.select().where(Tweet.is_published == True).count()

  Aggregates and GROUP BY

Use fn <#fn> to call aggregate functions and group_by() <#Select.group_by> to group:

    query = (User
             .select(User.username, fn.COUNT(Tweet.id).alias('tweet_count'))
             .join(Tweet, JOIN.LEFT_OUTER)
             .group_by(User.username)
             .order_by(SQL('tweet_count').desc()))
    for user in query:
        print(user.username, user.tweet_count)

Filter groups with having() <#Select.having>:

    # Users with more than 5 published tweets.
    query = (User
             .select(User.username, fn.COUNT(Tweet.id).alias('n'))
             .join(Tweet)
             .where(Tweet.is_published == True)
             .group_by(User.username)
             .having(fn.COUNT(Tweet.id) > 5))

  Scalar Values

scalar() <#SelectBase.scalar> executes a query and returns the first column of the first row as a Python value. Use it when a query produces a single number or string:

    oldest = Tweet.select(fn.MIN(Tweet.timestamp)).scalar()
    distinct_users = Tweet.select(fn.COUNT(Tweet.user.distinct())).scalar()

Pass as_tuple=True to retrieve multiple scalar columns:

    min_ts, max_ts = Tweet.select(
        fn.MIN(Tweet.timestamp),
        fn.MAX(Tweet.timestamp)
    ).scalar(as_tuple=True)

  Row Types

By default, SELECT queries return model instances. Four alternative row types are available by chaining a method before iteration:

•   dicts() <#BaseQuery.dicts>

•   tuples() <#BaseQuery.tuples>

•   namedtuples() <#BaseQuery.namedtuples>

•   objects() <#BaseQuery.objects>

    Example:

        # Dictionaries.
        for row in User.select().dicts():
            print(row)   # {'id': 1, 'username': 'charlie'}
        # Tuples.
        for row in User.select().tuples():
            print(row)   # (1, 'charlie')
        # Named tuples.
        for row in User.select().namedtuples():
            print(row.username)
        # Flatten any related data and return model instances.
        for row in User.select().objects():
            print(row.username)
        # Or pass a constructor callable.
        for row in User.select().objects(MyUserClass):
            print(row.my_username)

    Using tuples or dicts instead of model instances is faster for queries that produce many rows, because Peewee skips constructing model objects.

    objects() without an argument returns model instances but does not reconstruct the model graph from joined data, assigning all columns directly onto the primary model. This avoids the overhead of graph reconstruction when you have joined data and don't need nested model instances.

  Iterating Over Large Result Sets

For queries returning many rows, disable result caching with iterator() <#BaseQuery.iterator> to keep memory usage flat:

    # Combine iterator() with tuples() for maximum throughput.
    query = (Stat
             .select()
             .tuples()
             .iterator())
    for stat_tuple in query:
        write_to_file(stat_tuple)

When iterating over joined queries with .iterator(), use .objects() to avoid the overhead of model-graph reconstruction per row:

    query = (Tweet
             .select(Tweet.content, User.username)
             .join(User)
             .objects()
             .iterator())
    for tweet in query:
        print(tweet.username, tweet.content)

For maximum performance, execute the query and iterate the cursor directly:

    query = Tweet.select(Tweet.content, User.username).join(User)
    cursor = db.execute(query)
    for content, username in cursor:
        print(username, '->', content)

  Window Functions

A Window <#Window> function refers to an aggregate function that operates on a sliding window of data that is being processed as part of a SELECT query. Window functions make it possible to do things like:

1.  Perform aggregations against subsets of a result-set.

2.  Calculate a running total.

3.  Rank results.

4.  Compare a row value to a value in the preceding (or succeeding!) row(s).

    peewee comes with support for SQL window functions, which can be created by calling Function.over() <#Function.over> and passing in your partitioning or ordering parameters.

    For the following examples, we'll use the following model and sample data:

        class Sample(Model):
            counter = IntegerField()
            value = FloatField()
        data = [(1, 10),
                (1, 20),
                (2, 1),
                (2, 3),
                (3, 100)]
        Sample.insert_many(data, fields=[Sample.counter, Sample.value]).execute()

    Our sample table now contains:
    id | counter | value
    1 | 1 | 10.0
    2 | 1 | 20.0
    3 | 2 | 1.0
    4 | 2 | 3.0
    5 | 3 | 100.0

  Ordered Windows

Let's calculate a running sum of the value field. In order for it to be a "running" sum, we need it to be ordered, so we'll order with respect to the Sample's id field:

    query = Sample.select(
        Sample.counter,
        Sample.value,
        fn.SUM(Sample.value).over(order_by=[Sample.id]).alias('total'))
    for sample in query:
        print(sample.counter, sample.value, sample.total)
    # 1    10.    10.
    # 1    20.    30.
    # 2     1.    31.
    # 2     3.    34.
    # 3   100    134.

For another example, we'll calculate the difference between the current value and the previous value, when ordered by the id:

    difference = Sample.value - fn.LAG(Sample.value, 1).over(order_by=[Sample.id])
    query = Sample.select(
        Sample.counter,
        Sample.value,
        difference.alias('diff'))
    for sample in query:
        print(sample.counter, sample.value, sample.diff)
    # 1    10.   NULL
    # 1    20.    10.  -- (20 - 10)
    # 2     1.   -19.  -- (1 - 20)
    # 2     3.     2.  -- (3 - 1)
    # 3   100     97.  -- (100 - 3)

  Partitioned Windows

Let's calculate the average value for each distinct "counter" value. Notice that there are three possible values for the counter field (1, 2, and 3). We can do this by calculating the AVG() of the value column over a window that is partitioned depending on the counter field:

    query = Sample.select(
        Sample.counter,
        Sample.value,
        fn.AVG(Sample.value).over(partition_by=[Sample.counter]).alias('cavg'))
    for sample in query:
        print(sample.counter, sample.value, sample.cavg)
    # 1    10.    15.
    # 1    20.    15.
    # 2     1.     2.
    # 2     3.     2.
    # 3   100    100.

We can use ordering within partitions by specifying both the order_by and partition_by parameters. For an example, let's rank the samples by value within each distinct counter group.

    query = Sample.select(
        Sample.counter,
        Sample.value,
        fn.RANK().over(
            order_by=[Sample.value],
            partition_by=[Sample.counter]).alias('rank'))
    for sample in query:
        print(sample.counter, sample.value, sample.rank)
    # 1    10.    1
    # 1    20.    2
    # 2     1.    1
    # 2     3.    2
    # 3   100     1

  Bounded Windows

By default, window functions are evaluated using an unbounded preceding start for the window, and the current row as the end. We can change the bounds of the window our aggregate functions operate on by specifying a start and/or end in the call to Function.over() <#Function.over>. Additionally, Peewee comes with helper-methods on the Window <#Window> object for generating the appropriate boundary references:

•   Window.CURRENT_ROW <#Window.CURRENT_ROW> - attribute that references the current row.

•   Window.preceding() <#Window.preceding> - specify number of row(s) preceding, or omit number to indicate all preceding rows.

•   Window.following() <#Window.following> - specify number of row(s) following, or omit number to indicate all following rows.

    To examine how boundaries work, we'll calculate a running total of the value column, ordered with respect to id, but we'll only look the running total of the current row and it's two preceding rows:

        query = Sample.select(
            Sample.counter,
            Sample.value,
            fn.SUM(Sample.value).over(
                order_by=[Sample.id],
                start=Window.preceding(2),
                end=Window.CURRENT_ROW).alias('rsum'))
        for sample in query:
            print(sample.counter, sample.value, sample.rsum)
        # 1    10.    10.
        # 1    20.    30.  -- (20 + 10)
        # 2     1.    31.  -- (1 + 20 + 10)
        # 2     3.    24.  -- (3 + 1 + 20)
        # 3   100    104.  -- (100 + 3 + 1)

    Note: Technically we did not need to specify the end=Window.CURRENT because that is the default. It was shown in the example for demonstration.

    Let's look at another example. In this example we will calculate the "opposite" of a running total, in which the total sum of all values is decreased by the value of the samples, ordered by id. To accomplish this, we'll calculate the sum from the current row to the last row.

        query = Sample.select(
            Sample.counter,
            Sample.value,
            fn.SUM(Sample.value).over(
                order_by=[Sample.id],
                start=Window.CURRENT_ROW,
                end=Window.following()).alias('rsum'))
        # 1    10.   134.  -- (10 + 20 + 1 + 3 + 100)
        # 1    20.   124.  -- (20 + 1 + 3 + 100)
        # 2     1.   104.  -- (1 + 3 + 100)
        # 2     3.   103.  -- (3 + 100)
        # 3   100    100.  -- (100)

  Filtered Aggregates

Aggregate functions may also support filter functions (Postgres and Sqlite 3.25+), which get translated into a FILTER (WHERE...) clause. Filter expressions are added to an aggregate function with the Function.filter() <#Function.filter> method.

For an example, we will calculate the running sum of the value field with respect to the id, but we will filter-out any samples whose counter=2.

    query = Sample.select(
        Sample.counter,
        Sample.value,
        fn.SUM(Sample.value).filter(Sample.counter != 2).over(
            order_by=[Sample.id]).alias('csum'))
    for sample in query:
        print(sample.counter, sample.value, sample.csum)
    # 1    10.    10.
    # 1    20.    30.
    # 2     1.    30.
    # 2     3.    30.
    # 3   100    130.

Note: The call to filter() <#Function.filter> must precede the call to over() <#Function.over>.

  Reusing Window Definitions

If you intend to use the same window definition for multiple aggregates, you can create a Window <#Window> object. The Window <#Window> object takes the same parameters as Function.over() <#Function.over>, and can be passed to the over() method in-place of the individual parameters.

Here we'll declare a single window, ordered with respect to the sample id, and call several window functions using that window definition:

    win = Window(order_by=[Sample.id])
    query = Sample.select(
        Sample.counter,
        Sample.value,
        fn.LEAD(Sample.value).over(win),
        fn.LAG(Sample.value).over(win),
        fn.SUM(Sample.value).over(win)
    ).window(win)  # Include our window definition in query.
    for row in query.tuples():
        print(row)
    # counter  value  lead()  lag()  sum()
    # 1          10.     20.   NULL    10.
    # 1          20.      1.    10.    30.
    # 2           1.      3.    20.    31.
    # 2           3.    100.     1.    34.
    # 3         100.    NULL     3.   134.

  Multiple Window Definitions

In the previous example, we saw how to declare a Window <#Window> definition and re-use it for multiple different aggregations. You can include as many window definitions as you need in your queries, but it is necessary to ensure each window has a unique alias:

    w1 = Window(order_by=[Sample.id]).alias('w1')
    w2 = Window(partition_by=[Sample.counter]).alias('w2')
    query = Sample.select(
        Sample.counter,
        Sample.value,
        fn.SUM(Sample.value).over(w1).alias('rsum'),  # Running total.
        fn.AVG(Sample.value).over(w2).alias('cavg')   # Avg per category.
    ).window(w1, w2)  # Include our window definitions.
    for sample in query:
        print(sample.counter, sample.value, sample.rsum, sample.cavg)
    # counter  value   rsum     cavg
    # 1          10.     10.     15.
    # 1          20.     30.     15.
    # 2           1.     31.      2.
    # 2           3.     34.      2.
    # 3         100     134.    100.

Similarly, if you have multiple window definitions that share similar definitions, it is possible to extend a previously-defined window definition. For example, here we will be partitioning the data-set by the counter value, so we'll be doing our aggregations with respect to the counter. Then we'll define a second window that extends this partitioning, and adds an ordering clause:

    w1 = Window(partition_by=[Sample.counter]).alias('w1')
    # By extending w1, this window definition will also be partitioned
    # by "counter".
    w2 = Window(extends=w1, order_by=[Sample.value.desc()]).alias('w2')
    query = (Sample
             .select(Sample.counter, Sample.value,
                     fn.SUM(Sample.value).over(w1).alias('group_sum'),
                     fn.RANK().over(w2).alias('revrank'))
             .window(w1, w2)
             .order_by(Sample.id))
    for sample in query:
        print(sample.counter, sample.value, sample.group_sum, sample.revrank)
    # counter  value   group_sum   revrank
    # 1        10.     30.         2
    # 1        20.     30.         1
    # 2        1.      4.          2
    # 2        3.      4.          1
    # 3        100.    100.        1

  Frame Types: RANGE vs ROWS vs GROUPS

Depending on the frame type, the database will process ordered groups differently. Let's create two additional Sample rows to visualize the difference:

    >>> Sample.create(counter=1, value=20.)
    <Sample 6>
    >>> Sample.create(counter=2, value=1.)
    <Sample 7>

Our table now contains:
id | counter | value
1 | 1 | 10.0
2 | 1 | 20.0
3 | 2 | 1.0
4 | 2 | 3.0
5 | 3 | 100.0
6 | 1 | 20.0
7 | 2 | 1.0

Let's examine the difference by calculating a "running sum" of the samples, ordered with respect to the counter and value fields. To specify the frame type, we can use either:

•   Window.RANGE <#Window.RANGE>

•   Window.ROWS <#Window.ROWS>

•   Window.GROUPS <#Window.GROUPS>

    The behavior of RANGE <#Window.RANGE>, when there are logical duplicates, may lead to unexpected results:

        query = Sample.select(
            Sample.counter,
            Sample.value,
            fn.SUM(Sample.value).over(
                order_by=[Sample.counter, Sample.value],
                frame_type=Window.RANGE).alias('rsum'))
        for sample in query.order_by(Sample.counter, Sample.value):
            print(sample.counter, sample.value, sample.rsum)
        # counter  value   rsum
        # 1          10.     10.
        # 1          20.     50.
        # 1          20.     50.
        # 2           1.     52.
        # 2           1.     52.
        # 2           3.     55.
        # 3         100     155.

    With the inclusion of the new rows we now have some rows that have duplicate category and value values. The RANGE <#Window.RANGE> frame type causes these duplicates to be evaluated together rather than separately.

    The more expected result can be achieved by using ROWS <#Window.ROWS> as the frame-type:

        query = Sample.select(
            Sample.counter,
            Sample.value,
            fn.SUM(Sample.value).over(
                order_by=[Sample.counter, Sample.value],
                frame_type=Window.ROWS).alias('rsum'))
        for sample in query.order_by(Sample.counter, Sample.value):
            print(sample.counter, sample.value, sample.rsum)
        # counter  value   rsum
        # 1          10.     10.
        # 1          20.     30.
        # 1          20.     50.
        # 2           1.     51.
        # 2           1.     52.
        # 2           3.     55.
        # 3         100     155.

    Peewee uses these rules for determining what frame-type to use:

•   If the user specifies a frame_type, that frame type will be used.

•   If start and/or end boundaries are specified Peewee will default to using ROWS.

•   If the user did not specify frame type or start/end boundaries, Peewee will use the database default, which is RANGE.

    The Window.GROUPS <#Window.GROUPS> frame type looks at the window range specification in terms of groups of rows, based on the ordering term(s). Using GROUPS, we can define the frame so it covers distinct groupings of rows. Let's look at an example:

        query = (Sample
                 .select(Sample.counter, Sample.value,
                         fn.SUM(Sample.value).over(
                            order_by=[Sample.counter, Sample.value],
                            frame_type=Window.GROUPS,
                            start=Window.preceding(1)).alias('gsum'))
                 .order_by(Sample.counter, Sample.value))
        for sample in query:
            print(sample.counter, sample.value, sample.gsum)
        #  counter   value    gsum
        #  1         10       10
        #  1         20       50
        #  1         20       50   (10) + (20+20)
        #  2         1        42
        #  2         1        42   (20+20) + (1+1)
        #  2         3        5    (1+1) + 3
        #  3         100      103  (3) + 100

    As you can hopefully infer, the window is grouped by its ordering term, which is (counter, value). We are looking at a window that extends between one previous group and the current group.

    Note: For information about the window function APIs, see:

•   Function.over() <#Function.over>

•   Function.filter() <#Function.filter>

•   Window <#Window>

    For general information on window functions, read the postgres window functions tutorial <https://www.postgresql.org/docs/current/tutorial-window.html>

    Additionally, the postgres docs <https://www.postgresql.org/docs/current/sql-select.html#SQL-WINDOW> and the sqlite docs <https://www.sqlite.org/windowfunctions.html> contain a lot of good information.

  Common Table Expressions

A CTE factors out a subquery and gives it a name, making complex queries more readable and sometimes more efficient. CTEs also support recursion.

Define a CTE with cte() and include it with with_cte() <#Query.with_cte>:

  Simple Example

For an example, let's say we have some data points that consist of a key and a floating-point value. Let's define our model and populate some test data:

    class Sample(Model):
        key = TextField()
        value = FloatField()
    data = (
        ('a', (1.25, 1.5, 1.75)),
        ('b', (2.1, 2.3, 2.5, 2.7, 2.9)),
        ('c', (3.5, 3.5)))
    # Populate data.
    for key, values in data:
        Sample.insert_many([(key, value) for value in values],
                           fields=[Sample.key, Sample.value]).execute()

Let's use a CTE to calculate, for each distinct key, which values were above-average for that key.

    # First we'll declare the query that will be used as a CTE. This query
    # simply determines the average value for each key.
    cte = (Sample
           .select(Sample.key, fn.AVG(Sample.value).alias('avg_value'))
           .group_by(Sample.key)
           .cte('key_avgs', columns=('key', 'avg_value')))
    # Now we'll query the sample table, using our CTE to find rows whose value
    # exceeds the average for the given key. We'll calculate how far above the
    # average the given sample's value is, as well.
    query = (Sample
             .select(Sample.key, Sample.value)
             .join(cte, on=(Sample.key == cte.c.key))
             .where(Sample.value > cte.c.avg_value)
             .order_by(Sample.value)
             .with_cte(cte))

We can iterate over the samples returned by the query to see which samples had above-average values for their given group:

    >>> for sample in query:
    ...     print(sample.key, sample.value)
    # 'a', 1.75
    # 'b', 2.7
    # 'b', 2.9

  Complex Example

For a more complete example, let's consider the following query which uses multiple CTEs to find per-product sales totals in only the top sales regions. Our model looks like this:

    class Order(Model):
        region = TextField()
        amount = FloatField()
        product = TextField()
        quantity = IntegerField()

Here is how the query might be written in SQL. This example can be found in the postgresql documentation <https://www.postgresql.org/docs/current/static/queries-with.html>.

    WITH regional_sales AS (
        SELECT region, SUM(amount) AS total_sales
        FROM orders
        GROUP BY region
      ), top_regions AS (
        SELECT region
        FROM regional_sales
        WHERE total_sales > (SELECT SUM(total_sales) / 10 FROM regional_sales)
      )
    SELECT region,
           product,
           SUM(quantity) AS product_units,
           SUM(amount) AS product_sales
    FROM orders
    WHERE region IN (SELECT region FROM top_regions)
    GROUP BY region, product;

With Peewee, we would write:

    reg_sales = (Order
                 .select(Order.region,
                         fn.SUM(Order.amount).alias('total_sales'))
                 .group_by(Order.region)
                 .cte('regional_sales'))
    top_regions = (reg_sales
                   .select(reg_sales.c.region)
                   .where(reg_sales.c.total_sales > (
                       reg_sales.select(fn.SUM(reg_sales.c.total_sales) / 10)))
                   .cte('top_regions'))
    query = (Order
             .select(Order.region,
                     Order.product,
                     fn.SUM(Order.quantity).alias('product_units'),
                     fn.SUM(Order.amount).alias('product_sales'))
             .where(Order.region.in_(top_regions.select(top_regions.c.region)))
             .group_by(Order.region, Order.product)
             .with_cte(reg_sales, top_regions))

  Recursive CTEs

Peewee supports recursive CTEs. Recursive CTEs can be useful when, for example, you have a tree data-structure represented by a parent-link foreign key. Suppose, for example, that we have a hierarchy of categories for an online bookstore. We wish to generate a table showing all categories and their absolute depths, along with the path from the root to the category.

We'll assume the following model definition, in which each category has a foreign-key to its immediate parent category:

    class Category(Model):
        name = TextField()
        parent = ForeignKeyField('self', backref='children', null=True)

To list all categories along with their depth and parents, we can use a recursive CTE:

    # Define the base case of our recursive CTE. This will be categories that
    # have a null parent foreign-key.
    Base = Category.alias()
    level = Value(1).alias('level')
    path = Base.name.alias('path')
    base_case = (Base
                 .select(Base.id, Base.name, Base.parent, level, path)
                 .where(Base.parent.is_null())
                 .cte('base', recursive=True))
    # Define the recursive terms.
    RTerm = Category.alias()
    rlevel = (base_case.c.level + 1).alias('level')
    rpath = base_case.c.path.concat('->').concat(RTerm.name).alias('path')
    recursive = (RTerm
                 .select(RTerm.id, RTerm.name, RTerm.parent, rlevel, rpath)
                 .join(base_case, on=(RTerm.parent == base_case.c.id)))
    # The recursive CTE is created by taking the base case and UNION ALL with
    # the recursive term.
    cte = base_case.union_all(recursive)
    # We will now query from the CTE to get the categories, their levels,  and
    # their paths.
    query = (cte
             .select_from(cte.c.name, cte.c.level, cte.c.path)
             .order_by(cte.c.path))
    # We can now iterate over a list of all categories and print their names,
    # absolute levels, and path from root -> category.
    for category in query:
        print(category.name, category.level, category.path)
    # Example output:
    # root, 1, root
    # p1, 2, root->p1
    # c1-1, 3, root->p1->c1-1
    # c1-2, 3, root->p1->c1-2
    # p2, 2, root->p2
    # c2-1, 3, root->p2->c2-1

  Data-Modifying CTE

Peewee supports data-modifying CTE's.

Example of using a data-modifying CTE to move data from one table to an archive table, using a single query:

    class Event(Model):
        name = CharField()
        timestamp = DateTimeField()
    class Archive(Model):
        name = CharField()
        timestamp = DateTimeField()
    # Move rows older than 24 hours from the Event table to the Archive.
    cte = (Event
           .delete()
           .where(Event.timestamp < (datetime.now() - timedelta(days=1)))
           .returning(Event)
           .cte('moved_rows'))
    # Create a simple SELECT to get the resulting rows from the CTE.
    src = Select((cte,), (cte.c.id, cte.c.name, cte.c.timestamp))
    # Insert into the archive table whatever data was returned by the DELETE.
    res = (Archive
           .insert_from(src, (Archive.id, Archive.name, Archive.timestamp))
           .with_cte(cte)
           .execute())

The above corresponds to, roughly, the following SQL:

    WITH "moved_rows" AS (
        DELETE FROM "event" WHERE ("timestamp" < XXXX-XX-XXTXX:XX:XX)
        RETURNING "id", "name", "timestamp")
    INSERT INTO "archive" ("id", "name", "timestamp")
    SELECT "moved_rows"."id", "moved_rows"."name", "moved_rows"."timestamp"
    FROM "moved_rows";

For additional examples, refer to the tests in models.py and sql.py:

•   <https://github.com/coleifer/peewee/blob/master/tests/models.py>

•   <https://github.com/coleifer/peewee/blob/master/tests/sql.py>

  Writing Data

This document covers INSERT, UPDATE, and DELETE queries. Reading data is covered in Querying <#querying>.

All examples use the canonical schema from Models and Fields <#models>:

    import datetime
    from peewee import *
    db = SqliteDatabase(':memory:')
    class BaseModel(Model):
        class Meta:
            database = db
    class User(BaseModel):
        username = TextField(unique=True)
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(default=datetime.datetime.now)
        is_published = BooleanField(default=True)

Methods which will be discussed:
Query | Methods
INSERT | Model.create() <#Model.create> Model.insert() <#Model.insert> Model.insert_many() <#Model.insert_many> Model.insert_from() <#Model.insert_from> Model.replace() <#Model.replace> (Postgres unsupported) Model.replace_many() <#Model.replace_many> (Postgres unsupported)
UPDATE | Model.save() <#Model.save> Model.update() <#Model.update>
DELETE | Model.delete_instance() <#Model.delete_instance> Model.delete() <#Model.delete>

See also: Extensive library of SQL / Peewee examples <#query-library>

  Inserting Records

  Creating a single row

create() <#Model.create> inserts a row and returns the saved model instance:

    >>> charlie = User.create(username='charlie')
    >>> charlie.id
    1

This will INSERT a new row into the database. The primary key will automatically be retrieved and stored on the model instance.

Alternatively, instantiate the model and call save() <#Model.save>. The first call to save() on a new instance performs an INSERT:

    >>> user = User(username='huey')
    >>> user.save()
    1  # Returns number of rows modified.
    >>> user.id
    2

After the first save, the model instance holds its primary key. Any subsequent call to save() performs an UPDATE instead:

    >>> user.username = 'Huey'
    >>> user.save()
    1  # Returns number of rows updated.

For a foreign key field, pass either the related model instance or its raw primary key value:

    Tweet.create(user=huey, content='Hello!')
    Tweet.create(user=2, content='Also valid.')

To insert without constructing a model instance, use insert() <#Model.insert>. It returns the primary key of the new row:

    >>> User.insert(username='mickey').execute()
    3

  Bulk Inserts

Calling Model.create() <#Model.create> or Model.save() <#Model.save> in a loop should be avoided:

    data = [
        {'username': 'alice'},
        {'username': 'bob'},
        {'username': 'carol'},
    ]
    for data_dict in data:
        User.create(**data_dict)

The above is slow:

1.  Does not wrap the loop in a transaction. Result is each create() <#Model.create> happens in its own transaction <#transactions>.

2.  Python interpreter is getting in the way, and each Insert <#Insert> must be generated and parsed into SQL.

3.  Large amount of data (in terms of raw bytes of SQL) may be sent to the database to parse.

4.  Retrieving the last insert id, which may not be necessary.

    You can get a significant speedup by simply wrapping this in a transaction with atomic() <#Database.atomic>:

        with db.atomic():
            for data_dict in data:
                User.create(**data_dict)

    The fastest way to insert many rows is insert_many() <#Model.insert_many>. It accepts a list of dicts or tuples and emits a single multi-row INSERT:

        data = [
            {'username': 'alice'},
            {'username': 'bob'},
            {'username': 'carol'},
        ]
        User.insert_many(data).execute()
        # Tuples require an explicit field list:
        data = [('alice',), ('bob',), ('carol',)]
        User.insert_many(data, fields=[User.username]).execute()

    Optionally wrap the bulk insert in a transaction:

        with db.atomic():
            User.insert_many(data, fields=fields).execute()

    Insert queries support returning() with Postgresql and SQLite to obtain the inserted rows:

        query = (User
                 .insert_many([{'username': 'alice'}, {'username': 'bob'}])
                 .returning(User))
        for user in query:
            print(f'Added {user.username} with id = {user.id}')

  Batching large data sets

Depending on the number of rows in your data source, you may need to break it up into chunks. SQLite in particular may have a limit of 32766 <https://www.sqlite.org/limits.html#max_variable_number> variables-per-query (batch size would then be 32766 // row length).

You can write a loop to batch your data into chunks. It is strongly recommended you use a transaction <#transactions>:

    from peewee import chunked
    with db.atomic():
        for batch in chunked(data, 100):
            User.insert_many(batch).execute()

chunked() <#chunked> works on any iterable, including generators.

  Bulk-creating model instances

bulk_create() <#Model.bulk_create> accepts a list of unsaved model instances and inserts them efficiently. Pass batch_size to avoid hitting database limits:

    users = [User(username=f'user_{i}') for i in range(1000)]
    with db.atomic():
        User.bulk_create(users, batch_size=100)

Note: If you are using Postgresql (which supports the RETURNING clause), then the previously-unsaved model instances will have their new primary key values automatically populated. Other backends will not.

  Loading from another table

insert_from() <#Model.insert_from> generates an INSERT INTO ... SELECT query, copying rows from one table into another without round-tripping data through Python:

    res = (TweetArchive
           .insert_from(
               Tweet.select(Tweet.user, Tweet.message),
               fields=[TweetArchive.user, TweetArchive.message])
           .execute())

The above query is equivalent to the following SQL:

    INSERT INTO "tweet_archive" ("user_id", "message")
    SELECT "user_id", "message" FROM "tweet";

  Updating Records

  Updating a model instance

Modify attributes on a fetched instance and call save() <#Model.save> to persist the changes:

    charlie = User.get(User.username == 'charlie')
    charlie.username = 'charlie_admin'
    charlie.save()   # Issues UPDATE WHERE id = charlie.id

By default, save() re-saves all fields. To only emit changed fields, set only_save_dirty = True in the model's Meta, or pass only the fields you want to update:

    charlie.username = 'charlie_v2'
    charlie.save(only=[User.username])

Note: If a model instance does not have a primary key, the first call to save() <#Model.save> will perform an INSERT query.

Once a model instance has a primary key, subsequent calls to save() <#Model.save> result in an UPDATE.

  Updating multiple rows

update() <#Model.update> issues a single UPDATE that affects every row matching the WHERE clause:

    # Publish all unpublished tweets older than one week.
    one_week_ago = datetime.datetime.now() - datetime.timedelta(days=7)
    nrows = (Tweet
             .update(is_published=True)
             .where(
                 (Tweet.is_published == False) &
                 (Tweet.timestamp < one_week_ago))
             .execute())

The return value is the number of rows affected.

Update queries support returning() with Postgresql and SQLite to obtain the updated rows:

    query = (User
             .update(spam=True)
             .where(User.username.contains('billing'))
             .returning(User))
    for user in query:
        print(f'Marked {user.username} as spam')

Because UPDATE queries do not support joins, we can use subqueries to update rows based on values in related tables. For example, unpublish all tweets by users with 'billing' in their username:

    spammers = User.select().where(User.username.contains('billing'))
    (Tweet
     .update(is_published=False)
     .where(Tweet.user.in_(spammers))
     .execute())

  Atomic updates

Use column expressions in update() to modify values without a read-modify-write cycle. Performing updates atomically prevents race-conditions:

    # WRONG: reads each row into Python, increments, then saves.
    # Vulnerable to race conditions; slow on many rows.
    for stat in Stat.select().where(Stat.url == url):
        stat.counter += 1
        stat.save()
    # CORRECT: single UPDATE statement, atomic at the database level.
    Stat.update(counter=Stat.counter + 1).where(Stat.url == url).execute()

Any SQL expression is valid on the right-hand side:

    # Give every employee a 10% salary bonus added to their existing bonus.
    Employee.update(bonus=Employee.bonus + (Employee.salary * 0.10)).execute()
    # Denormalize a count column from a subquery.
    tweet_count = (Tweet
                   .select(fn.COUNT(Tweet.id))
                   .where(Tweet.user == User.id))
    User.update(num_tweets=tweet_count).execute()

  Bulk-updating model instances

When you have a list of modified model instances and want to update specific fields across all of them in one query, use bulk_update() <#Model.bulk_update>:

    u1, u2, u3 = User.select().limit(3)
    u1.username = 'u1-new'
    u2.username = 'u2-new'
    u3.username = 'u3-new'
    User.bulk_update([u1, u2, u3], fields=[User.username])

This emits a single UPDATE using a SQL CASE expression. For large lists, specify a batch_size and wrap in a transaction:

    with db.atomic():
        User.bulk_update(users, fields=[User.username], batch_size=50)

Note: bulk_update may be slower than a direct UPDATE query when the list is very large, because the generated CASE expression grows proportionally. For updates that can be expressed as a single WHERE clause, the direct update() <#Model.update> approach is faster.

  Upsert

An upsert (INSERT or UPDATE) inserts a new row, or if a unique constraint would be violated, updates the existing row instead.

Peewee provides two complementary approaches.

  on_conflict_replace - SQLite and MySQL

SQLite and MySQL support a REPLACE query, which will replace the row in the event of a conflict:

    class User(BaseModel):
        username = TextField(unique=True)
        last_login = DateTimeField(null=True)
    # Insert, or replace the entire existing row.
    User.replace(username='huey', last_login=datetime.datetime.now()).execute()
    # Equivalent using insert():
    (User
     .insert(username='huey', last_login=datetime.datetime.now())
     .on_conflict_replace()
     .execute())

Warning: replace deletes and re-inserts, which changes the primary key. Use on_conflict (below) when the primary key must be preserved, or when only some columns should be updated.

  on_conflict - all backends

The on_conflict() <#Insert.on_conflict> method is much more powerful.

    class User(BaseModel):
        username = TextField(unique=True)
        last_login = DateTimeField(null=True)
        login_count = IntegerField(default=0)
    now = datetime.datetime.now()
    (User
     .insert(username='huey', last_login=now, login_count=1)
     .on_conflict(
         # Postgresql and SQLite require identifying the conflicting constraint.
         # MySQL does not need this.
         conflict_target=[User.username],
         # Columns whose values should come from the incoming row:
         preserve=[User.last_login],
         # Columns to update using an expression:
         update={User.login_count: User.login_count + 1})
     .execute())

Calling this query repeatedly will increment login_count atomically and update last_login on each call, without creating duplicate rows.

The EXCLUDED namespace references the values that would have been inserted if the constraint had not fired. This allows conditional updates:

    class KV(BaseModel):
        key = TextField(unique=True)
        value = IntegerField()
    KV.create(key='k1', value=1)
    # Demonstrate usage of EXCLUDED.
    # Here we will attempt to insert a new value for a given key. If that
    # key already exists, then we will update its value with the *sum* of its
    # original value and the value we attempted to insert -- provided that
    # the new value is larger than the original value.
    query = (KV.insert(key='k1', value=10)
             .on_conflict(conflict_target=[KV.key],
                          update={KV.value: KV.value + EXCLUDED.value},
                          where=(EXCLUDED.value > KV.value)))
    # Executing the above query will result in the following data being
    # present in the "kv" table:
    # (key='k1', value=11)
    query.execute()
    # If we attempted to execute the query *again*, then nothing would be
    # updated, as the new value (10) is now less than the value in the
    # original row (11).

There are several important concepts to understand when using ON CONFLICT:

•   conflict_target=: which column(s) have the UNIQUE constraint. For a user table, this might be the user's email (SQLite and Postgresql only).

•   preserve=: if a conflict occurs, this parameter is used to indicate which values from the new data we wish to update.

•   update=: if a conflict occurs, this is a mapping of data to apply to the pre-existing row.

•   EXCLUDED: this "magic" namespace allows you to reference the new data that would have been inserted if the constraint hadn't failed.

    Full example:

        class User(Model):
            email = CharField(unique=True)  # Unique identifier for user.
            last_login = DateTimeField()
            login_count = IntegerField(default=0)
            ip_log = TextField(default='')
        # Demonstrates the above 4 concepts.
        def login(email, ip):
            rowid = (User
                     .insert({User.email: email,
                              User.last_login: datetime.now(),
                              User.login_count: 1,
                              User.ip_log: ip})
                     .on_conflict(
                         # If the INSERT fails due to a constraint violation on the
                         # user email, then perform an UPDATE instead.
                         conflict_target=[User.email],
                         # Set the "last_login" to the value we would have inserted
                         # (our call to datetime.now()).
                         preserve=[User.last_login],
                         # Increment the user's login count and prepend the new IP
                         # to the user's ip history.
                         update={User.login_count: User.login_count + 1,
                                 User.ip_log: fn.CONCAT(EXCLUDED.ip_log, ',', User.ip_log)})
                     .execute())
            return rowid
        # This will insert the initial row, returning the new row id (1).
        print(login('test@example.com', '127.1'))
        # Because test@example.com exists, this will trigger the UPSERT. The row id
        # from above is returned again (1).
        print(login('test@example.com', '127.2'))
        u = User.get()
        print(u.login_count, u.ip_log)
        # Prints "2 127.2,127.1"

    See also: Insert.on_conflict() <#Insert.on_conflict> and OnConflict <#OnConflict>.

  on_conflict_ignore

Insert the row, and silently do nothing if a constraint would be violated:

    # Insert if username does not exist; ignore if it does.
    User.insert(username='huey').on_conflict_ignore().execute()

Supported by SQLite, MySQL, and Postgresql.

  Deleting Records

Delete a single fetched instance with delete_instance() <#Model.delete_instance>:

    tweet = Tweet.get_by_id(42)
    tweet.delete_instance()   # Returns number of rows deleted.

To delete a row along with all dependent rows (rows in other tables that reference it via foreign key), pass recursive=True:

    # Deletes the user and all their tweets, favorites, etc.
    with db.atomic():
        user.delete_instance(recursive=True)

Warning: recursive=True works by querying for dependent rows and deleting them first - it does not rely on ON DELETE CASCADE. For large graphs of related data, this can be slow. Be sure to wrap calls in a transaction <#transactions> and consider using database-level cascade constraints on the foreign keys.

To delete an arbitrary set of rows without fetching them:

    # Delete all unpublished tweets older than 30 days.
    cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
    nrows = (Tweet
             .delete()
             .where(
                 (Tweet.is_published == False) &
                 (Tweet.timestamp < cutoff))
             .execute())

Delete queries support returning() with Postgresql and SQLite to obtain the deleted rows:

    query = (User
             .delete()
             .where(User.username.contains('billing'))
             .returning(User))
    for user in query:
        print(f'Deleted: {user.username}')

Because DELETE queries do not support joins, we can use subqueries to delete rows based on values in related tables. For example, delete all tweets by users with 'billing' in their username:

    spammers = User.select().where(User.username.contains('billing'))
    (Tweet
     .delete()
     .where(Tweet.user.in_(spammers))
     .execute())

See also:

•   Model.delete_instance() <#Model.delete_instance>

•   Model.delete() <#Model.delete>

•   Delete <#Delete>

  Returning Clause

PostgresqlDatabase <#PostgresqlDatabase> and SqliteDatabase <#SqliteDatabase> (3.35.0+) support a RETURNING clause on UPDATE, INSERT and DELETE queries. Specifying a RETURNING clause allows you to iterate over the rows accessed by the query.

By default, the return values upon execution of the different queries are:

•   INSERT - auto-incrementing primary key value of the newly-inserted row. When not using an auto-incrementing primary key, Postgres will return the new row's primary key, but SQLite and MySQL will not.

•   UPDATE - number of rows modified

•   DELETE - number of rows deleted

    When a returning clause is used the return value upon executing a query will be an iterable cursor object, providing access to data that was inserted, updated or deleted by the query.

    For example, let's say you have an Update <#Update> that deactivates all user accounts whose registration has expired. After deactivating them, you want to send each user an email letting them know their account was deactivated. Rather than writing two queries, a SELECT and an UPDATE, you can do this in a single UPDATE query with a RETURNING clause:

        query = (User
                 .update(is_active=False)
                 .where(User.registration_expired == True)
                 .returning(User))
        # Send an email to every user that was deactivated.
        for deactivate_user in query.execute():
            send_deactivation_email(deactivated_user.email)
        query = (User
                 .delete()
                 .where(User.is_spam == True)
                 .returning(User.id))
        for user in query.execute():
            print(f'Deleted spam user id: {user.id}')

    The RETURNING clause is available on:

•   Insert <#Insert>

•   Update <#Update>

•   Delete <#Delete>

    As another example, let's add a user and set their creation-date to the server-generated current timestamp. We'll create and retrieve the new user's ID, Email and the creation timestamp in a single query:

        query = (User
                 .insert(email='foo@bar.com', created=fn.now())
                 .returning(User))  # Shorthand for all columns on User.
        # When using RETURNING, execute() returns a cursor.
        cursor = query.execute()
        # Get the user object we just inserted and log the data:
        user = cursor[0]
        logger.info('Created user %s (id=%s) at %s', user.email, user.id, user.created)

    By default the cursor will return Model <#Model> instances, but you can specify a different row type:

        data = [{'name': 'charlie'}, {'name': 'huey'}, {'name': 'mickey'}]
        query = (User
                 .insert_many(data)
                 .returning(User.id, User.username)
                 .dicts())
        for new_user in query.execute():
            print('Added user "%s", id=%s' % (new_user['username'], new_user['id']))

    Just as with Select <#Select> queries, you can specify various result row types <#row-types>.

  Query Operators

The following types of comparisons are supported by peewee:
Comparison | Meaning
== | x equals y
!= | x is not equal to y
< | x is less than y
<= | x is less than or equal to y
> | x is greater than y
>= | x is greater than or equal to y
<< | x IN y, where y is a list or query
>> | x IS y, where y is None/NULL
% | x LIKE y where y may contain wildcards
** | x ILIKE y where y may contain wildcards
^ | x XOR y
~ | Unary negation (e.g., NOT x)

Additional operations are provided as methods:
Method | Meaning
.in_(value) | IN lookup (identical to <<).
.not_in(value) | NOT IN lookup.
.is_null(is_null) | IS NULL or IS NOT NULL. Accepts boolean param.
.contains(substr) | Wild-card search for substring.
.startswith(prefix) | Search for values beginning with prefix.
.endswith(suffix) | Search for values ending with suffix.
.between(low, high) | Search where low <= value <= high.
.regexp(exp) | Regular expression match (case-sensitive).
.iregexp(exp) | Regular expression match (case-insensitive).
.bin_and(value) | Binary AND.
.bin_or(value) | Binary OR.
.concat(other) | Concatenate two strings or objects using ||.
.distinct() | Mark column for DISTINCT selection.
.collate(collation) | Specify column with the given collation.
.cast(type) | Cast the value of the column to the given type.

To combine clauses using logical operators, use:
Operator | Meaning | Example
& | AND | (User.is_active == True) & (User.is_admin == True)
| (pipe) | OR | (User.is_admin) | (User.is_superuser)
~ | NOT (unary negation) | ~(User.username.contains('admin'))

Here is how you might use some of these query operators:

    # Find the user whose username is "charlie".
    User.select().where(User.username == 'charlie')
    # Find the users whose username is in [charlie, huey, mickey]
    User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
    # Find users whose salary is between 50k and 60k (inclusive).
    Employee.select().where(Employee.salary.between(50000, 60000))
    Employee.select().where(Employee.name.startswith('C'))
    Blog.select().where(Blog.title.contains(search_string))

Here is how you might combine expressions. Comparisons can be arbitrarily complex.

Note: Comparisons must be wrapped in parentheses due to Python operator precedence rules.

    # Find any users who are active administrations.
    User.select().where(
      (User.is_admin == True) &
      (User.is_active == True))
    # Find any users who are either administrators or super-users.
    User.select().where(
      (User.is_admin == True) |
      (User.is_superuser == True))
    # Alternatively, use the boolean values directly. Here we query users who
    # are admins and NOT superusers.
    User.select().where(User.is_admin & ~User.is_superuser)
    # Find any Tweets by users who are not admins (NOT IN).
    admins = User.select().where(User.is_admin == True)
    non_admin_tweets = Tweet.select().where(Tweet.user.not_in(admins))
    # Find any users who are not my friends (strangers).
    friends = User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
    strangers = User.select().where(User.id.not_in(friends))

Warning: Although you may be tempted to use python's in, and, or, is, and not operators in your query expressions, these will not work. The return value of an in expression is always coerced to a boolean value. Similarly, and, or and not all treat their arguments as boolean values and cannot be overloaded.

So just remember:

•   Use .in_() and .not_in() instead of in and not in

•   Use & instead of and

•   Use | instead of or

•   Use ~ instead of not

•   Use .is_null() instead of is None or == None.

•   Use == and != for comparing against True and False, or you may use the implicit value of the expression.

•   Don't forget to wrap comparisons in parentheses when using logical operators.

    For more examples, see the Expressions section.

    Note: LIKE and ILIKE with SQLite

    Because SQLite's LIKE operation is case-insensitive by default, peewee will use the SQLite GLOB operation for case-sensitive searches. The glob operation uses asterisks for wildcards as opposed to the usual percent-sign. If you are using SQLite and want case-sensitive partial string matching, remember to use asterisks for the wildcard.

  Three Valued Logic

Because of the way SQL handles NULL, there are some special operations available for expressing:

•   IS NULL

•   IS NOT NULL

•   IN

•   NOT IN

    While it would be possible to use the IS NULL and IN operators with the negation operator (~), sometimes to get the correct semantics you will need to explicitly use IS NOT NULL and NOT IN.

    The simplest way to use IS NULL and IN is to use the operator overloads:

        # Get all User objects whose last login is NULL.
        User.select().where(User.last_login >> None)
        # Get users whose username is in the given list.
        usernames = ['charlie', 'huey', 'mickey']
        User.select().where(User.username << usernames)

    If you don't like operator overloads, you can call the Field methods instead:

        # Get all User objects whose last login is NULL.
        User.select().where(User.last_login.is_null(True))
        # Get users whose username is in the given list.
        usernames = ['charlie', 'huey', 'mickey']
        User.select().where(User.username.in_(usernames))

    To negate the above queries, you can use unary negation, but for the correct semantics use the special IS NOT and NOT IN operators:

        # Get all User objects whose last login is *NOT* NULL.
        User.select().where(User.last_login.is_null(False))
        # Get users whose username is *NOT* in the given list.
        usernames = ['charlie', 'huey', 'mickey']
        User.select().where(User.username.not_in(usernames))

  User-Defined Operators

Because I ran out of python operators to overload, there are some missing operators in peewee, for instance modulo. If you find that you need to support an operator that is not in the table above, it is very easy to add your own.

Here is how you might add support for modulo in SQLite:

    from peewee import *
    from peewee import Expression  # The building block for expressions.
    def mod(lhs, rhs):
        # Note: this works with Sqlite, but some drivers may use string-
        # formatting before sending the query to the database, so you may
        # need to use '%%' instead here.
        return Expression(lhs, '%', rhs)

Now you can use these custom operators to build richer queries:

    # Users with even ids.
    User.select().where(mod(User.id, 2) == 0)

  Expressions

Peewee is designed to provide a simple, expressive, and pythonic way of constructing SQL queries. This section will provide a quick overview of some common types of expressions.

Two common types of objects that are composed to create expressions:

•   Field <#Field> instances

•   SQL aggregations and functions using fn <#fn>

    We will assume a simple "User" model with fields for username and other things. It looks like this:

        class User(Model):
            username = CharField()
            is_admin = BooleanField()
            is_active = BooleanField()
            last_login = DateTimeField()
            login_count = IntegerField()
            failed_logins = IntegerField()

    Comparisons use the Query Operators:

        # username is equal to 'charlie'
        User.username == 'charlie'
        # user has logged in less than 5 times
        User.login_count < 5

    Comparisons can be combined using bitwise and and or. Operator precedence is controlled by python and comparisons can be nested to an arbitrary depth:

        # User is both and admin and has logged in today
        (User.is_admin == True) & (User.last_login >= today)
        # User's username is either charlie or charles
        (User.username == 'charlie') | (User.username == 'charles')
        # User is active and not a superuser.
        (User.is_active & ~User.is_superuser)

    Comparisons can be used with functions as well:

        # user's username starts with a 'g' or a 'G':
        fn.Lower(fn.Substr(User.username, 1, 1)) == 'g'

    We can do some fairly interesting things, as expressions can be compared against other expressions. Expressions also support arithmetic operations:

        # users who entered the incorrect more than half the time and have logged
        # in at least 10 times
        (User.failed_logins > (User.login_count * .5)) & (User.login_count > 10)

    Expressions allow us to do atomic updates:

        # when a user logs in we want to increment their login count:
        User.update(login_count=User.login_count + 1).where(User.id == user_id)

    Expressions can be used in all parts of a query, so experiment!

  Row values

Many databases support row values <https://www.sqlite.org/rowvalue.html>, which are similar to Python tuple objects. In Peewee, it is possible to use row-values in expressions via Tuple <#Tuple>. For example,

    # If for some reason your schema stores dates in separate columns ("year",
    # "month" and "day"), you can use row-values to find all rows that happened
    # in a given month:
    Tuple(Event.year, Event.month) == (2019, 1)

The more common use for row-values is to compare against multiple columns from a subquery in a single expression. There are other ways to express these types of queries, but row-values may offer a concise and readable approach.

For example, assume we have a table "EventLog" which contains an event type, an event source, and some metadata. We also have an "IncidentLog", which has incident type, incident source, and metadata columns. We can use row-values to correlate incidents with certain events:

    class EventLog(Model):
        event_type = TextField()
        source = TextField()
        data = TextField()
        timestamp = TimestampField()
    class IncidentLog(Model):
        incident_type = TextField()
        source = TextField()
        traceback = TextField()
        timestamp = TimestampField()
    # Get a list of all the incident types and sources that have occured today.
    incidents = (IncidentLog
                 .select(IncidentLog.incident_type, IncidentLog.source)
                 .where(IncidentLog.timestamp >= datetime.date.today()))
    # Find all events that correlate with the type and source of the
    # incidents that occured today.
    events = (EventLog
              .select()
              .where(Tuple(EventLog.event_type, EventLog.source).in_(incidents))
              .order_by(EventLog.timestamp))

Other ways to express this type of query would be to use a join <#relationships> or to join on a subquery <#join-subquery>. The above example is there just to give you and idea how Tuple <#Tuple> might be used.

You can also use row-values to update multiple columns in a table, when the new data is derived from a subquery. For an example, see here <https://www.sqlite.org/rowvalue.html#update_multiple_columns_of_a_table_based_on_a_query>.

  SQL Functions

SQL functions, like COUNT() or SUM(), can be expressed using the fn() <#fn> helper:

    # Get all users and the number of tweets they've authored. Sort the
    # results from most tweets -> fewest tweets.
    query = (User
             .select(User, fn.COUNT(Tweet.id).alias('tweet_count'))
             .join(Tweet, JOIN.LEFT_OUTER)
             .group_by(User)
             .order_by(fn.COUNT(Tweet.id).desc()))
    for user in query:
        print('%s -- %s tweets' % (user.username, user.tweet_count))

The fn helper exposes any SQL function as if it were a method. The parameters can be fields, values, subqueries, or even nested functions.

  Nesting function calls

Suppose you need to want to get a list of all users whose username begins with a. There are a couple ways to do this, but one method might be to use some SQL functions like LOWER and SUBSTR. To use arbitrary SQL functions, use the special fn() <#fn> object to construct queries:

    # Select the user's id, username and the first letter of their username, lower-cased
    first_letter = fn.LOWER(fn.SUBSTR(User.username, 1, 1))
    query = User.select(User, first_letter.alias('first_letter'))
    # Alternatively we could select only users whose username begins with 'a'
    a_users = User.select().where(first_letter == 'a')
    for user in a_users:
        print(user.username)

  SQL Helper

There are times when you may want to simply pass in some arbitrary sql. You can do this using the special SQL <#SQL> class. One use-case is when referencing an alias:

    # We'll query the user table and annotate it with a count of tweets for
    # the given user
    query = (User
             .select(User, fn.Count(Tweet.id).alias('ct'))
             .join(Tweet)
             .group_by(User))
    # Now we will order by the count, which was aliased to "ct"
    query = query.order_by(SQL('ct'))
    # You could, of course, also write this as:
    query = query.order_by(fn.COUNT(Tweet.id))

There are two ways to execute hand-crafted SQL statements with peewee:

1.  Database.execute_sql() <#Database.execute_sql> for executing any type of query

2.  RawQuery <#RawQuery> for executing SELECT queries and returning model instances.

  Security and SQL Injection

By default peewee will parameterize queries, so any parameters passed in by the user will be escaped. The only exception to this rule is if you are writing a raw SQL query or are passing in a SQL object which may contain untrusted data. To mitigate this, ensure that any user-defined data is passed in as a query parameter and not part of the actual SQL query:

    # Bad! DO NOT DO THIS!
    query = MyModel.raw('SELECT * FROM my_table WHERE data = %s' % user_data)
    # Bad! DO NOT DO THIS!
    query = MyModel.select().where(SQL('Some SQL expression %s' % user_data))

Use parameters to prevent SQL injection:

    # Good. `user_data` will be treated as a parameter to the query.
    query = MyModel.raw('SELECT * FROM my_table WHERE data = %s', user_data)
    # Good. `user_data` will be treated as a parameter.
    query = MyModel.select().where(SQL('Some SQL expression %s', user_data))

Note: MySQL and Postgresql use '%s' to denote parameters. SQLite, on the other hand, uses '?'. Be sure to use the character appropriate to your database. You can also find this parameter by checking Database.param.

  Transactions

A transaction groups one or more SQL statements into a single unit of work. Either all of the statements succeed and are committed to the database, or none of them are - the database rolls back to the state it was in before the transaction began.

Peewee operates in autocommit mode: every statement that runs outside an explicit transaction runs in its own implicit transaction. To group statements, use the tools described in this document.

  db.atomic

Database.atomic() <#Database.atomic> is the recommended transaction API. atomic() <#Database.atomic> can be used as a context manager or a decorator, and it handles nesting automatically.

If an unhandled exception occurs in a wrapped block, the current block will be rolled back. Otherwise the statements will be committed at the end of the block.

As a context manager:

    with db.atomic() as txn:
        user = User.create(username='charlie')
        tweet = Tweet.create(user=user, content='Hello')
    # Both rows are committed when block exits normally.

As a decorator:

    @db.atomic()
    def create_user_with_tweet(username, content):
        user = User.create(username=username)
        Tweet.create(user=user, content=content)
        return user

If an unhandled exception propagates out of the block, the transaction (or savepoint - see below) is rolled back and the exception continues to propagate:

    with db.atomic() as txn:
        User.create(username='huey')
        # User has been INSERTed into the database but the transaction is not
        # yet committed because we haven't left the scope of the "with" block.
        raise ValueError('something went wrong')
        # This exception is unhandled - the transaction will be rolled-back and
        # the ValueError will be raised.
    # User('huey') was NOT committed, the transaction rolled-back.
    # The ValueError is raised here.

  Manual Commit / Rollback

You can commit or roll-back explicitly inside an atomic() <#Database.atomic> block. After calling commit() or rollback() a new transaction (or savepoint) begins automatically:

    with db.atomic() as txn:
        try:
            save_objects()
        except SaveError:
            txn.rollback()  # Roll back, new transaction starts automatically.
            log_error()
        finalize()  # Runs in a new transaction.
    # finalize()'s changes are committed here.

  Nesting Transactions

The outermost atomic() <#Database.atomic> block creates a transaction. Any nested atomic() blocks create savepoints instead. A savepoint is a named point within a transaction to which you can roll back without affecting the rest of the transaction.

    with db.atomic():                        # Transaction begins.
        User.create(username='charlie')
        with db.atomic() as sp:              # Savepoint begins.
            User.create(username='huey')
            sp.rollback()                    # Rolls back huey only.
            User.create(username='alice')    # New savepoint begins here.
        User.create(username='mickey')
    # Committed: charlie, alice, mickey. huey was rolled back.

Savepoints can be nested arbitrarily deep:

    with db.atomic():
        with db.atomic():
            with db.atomic() as inner:
                do_something_risky()
                inner.rollback()   # Only the innermost work is lost.
            do_something_safe()

Note: atomic() tracks the nesting depth internally. You do not need to manage savepoint names or transaction state manually.

  Explicit Transaction

Database.transaction() <#Database.transaction> opens an explicit transaction that does not nest. Any transaction() call inside an outer transaction() block is ignored - only the outermost transaction is active.

Use this only when you explicitly need a flat, non-nesting transaction. For most cases, atomic() is the better choice.

If an exception occurs in a wrapped block, the transaction will be rolled back. Otherwise the statements will be committed at the end of the wrapped block.

    with db.transaction() as txn:
        User.create(username='mickey')
        txn.commit()         # Commit now; a new transaction begins.
        User.create(username='huey')
        txn.rollback()       # Roll back huey; a new transaction begins.
        User.create(username='zaizee')
    # zaizee is committed when the block exits.

Warning: If you attempt to nest transactions with peewee using the transaction() <#Database.transaction> context manager, only the outer-most transaction will be used.

As this may lead to unpredictable behavior, it is recommended that you use atomic() <#Database.atomic>.

  Explicit Savepoints

Database.savepoint() <#Database.savepoint> creates a savepoint within an active transaction. Savepoints must occur within a transaction, but can be nested arbitrarily deep.

    with db.transaction() as txn:
        with db.savepoint() as sp:
            User.create(username='mickey')
        with db.savepoint() as sp2:
            User.create(username='zaizee')
            sp2.rollback()  # "zaizee" is not saved.
            User.create(username='huey')
    # mickey and huey were created.

Note: If you manually commit or roll back a savepoint, a new savepoint will automatically begin.

  Autocommit Mode

Peewee requires the underlying driver to run in autocommit mode and manages transaction boundaries itself. This differs from the DB-API 2.0 default, which starts a transaction implicitly and requires you to commit manually. As a result, Peewee puts all DB-API drivers into autocommit mode.

In rare cases where you need to take direct control of BEGIN/COMMIT/ ROLLBACK - bypassing Peewee's transaction management entirely - use manual_commit() <#Database.manual_commit>:

    with db.manual_commit():
        db.begin()  # Begin transaction explicitly.
        try:
            user.delete_instance(recursive=True)
        except:
            db.rollback()  # Rollback! An error occurred.
            raise
        else:
            try:
                db.commit()  # Commit changes.
            except:
                db.rollback()
                raise

manual_commit suspends Peewee's transaction management for the duration of the block. atomic() and transaction() have no effect inside it. This should rarely be needed in application code.

  SQLite Transaction Locking Modes

SQLite supports three locking modes for transactions. Use these when precise control over read-write locking is required:

    with db.atomic('EXCLUSIVE'):
        # No other connection can read or write until this commits.
        do_something()
    @db.atomic('IMMEDIATE')
    def load_data():
        # No other writer is allowed, but readers can proceed.
        insert_records()

The three modes:

•   DEFERRED (default) - acquires the minimum necessary lock as reads and writes occur. Another writer can intervene between BEGIN and your first write.

•   IMMEDIATE - acquires a write reservation lock at BEGIN. Other writers are blocked; readers can proceed.

•   EXCLUSIVE - acquires an exclusive lock at BEGIN. No other connection can read or write until the transaction completes.

    See also: SQLite locking documentation <https://sqlite.org/lockingv3.html>.

  Postgresql Isolation Notes

Postgresql supports configurable isolation levels per-transaction, from least to most strict:

•   READ UNCOMMITTED

•   READ COMMITTED (default in most deployments)

•   REPEATABLE READ

•   SERIALIZABLE

    See the Postgresql transaction isolation docs <https://www.postgresql.org/docs/current/transaction-iso.html> for discussion.

    The default isolation level is specified when initializing PostgresqlDatabase <#PostgresqlDatabase>:

        db = PostgresqlDatabase(
            'my_app',
            user='postgres',
            host='10.8.0.1',
            port=5432,
            isolation_level='SERIALIZABLE')
        # Or use the constants provided by the driver.
        from psycopg2.extensions import ISOLATION_LEVEL_SERIALIZABLE
        db = PostgresqlDatabase(
            ...
            isolation_level=ISOLATION_LEVEL_SERIALIZABLE)
        from psycopg import IsolationLevel
        db = PostgresqlDatabase(
            ...
            isolation_level=IsolationLevel.SERIALIZABLE)

    To control the isolation-level for a transaction, you can pass the desired setting to the outer-most atomic() block:

        with db.atomic('SERIALIZABLE') as txn:
            ...

    Note: Isolation level cannot be specified for nested atomic() blocks.

  Schema Management

This document covers creating and dropping tables, managing indexes and constraints after the fact, and evolving a schema over time.

  Creating Tables

Create tables for a list of models with Database.create_tables() <#Database.create_tables>:

    db.create_tables([User, Tweet, Favorite])

By default Peewee uses CREATE TABLE IF NOT EXISTS, making it safe to call on application startup. To disable this, pass safe=False.

    db.create_tables([User, Tweet, Favorite], safe=False)

To create a single table:

    Tweet.create_table()

Database.create_tables() <#Database.create_tables> respects foreign key dependencies: if Tweet references User, User's table is created first regardless of the order in which they appear in the list.

Indexes declared in Meta.indexes and via Model.add_index() <#Model.add_index> are created along with the table.

Note: A common pattern in web applications is to call db.create_tables(MODELS, safe=True) once at startup. This ensures all tables exist without failing on an already- initialized database. It does not apply schema changes - for that, see Schema Migrations.

  Dropping Tables

    db.drop_tables([User, Tweet, Favorite])

By default Peewee uses DROP TABLE IF EXISTS, making it safe to call multiple times. To disable this, pass safe=False.

    db.drop_tables([User, Tweet, Favorite], safe=False)

Pass cascade=True (Postgresql and MySQL) to let the database handle dependency ordering:

    db.drop_tables([User, Tweet, Favorite], cascade=True)

To drop a single table:

    User.drop_table()

  SchemaManager

SchemaManager <#SchemaManager> provides finer-grained control over DDL operations. Each model exposes an instance at Model._schema.

Creating and dropping indexes independently:

    # Create just the indexes for a model (table already exists).
    User._schema.create_indexes()
    # Drop a specific index.
    User._schema.drop_index(User.username)

Adding a foreign key constraint after table creation (useful when circular foreign keys are involved - see Circular foreign key dependencies <#circular-fks> in the models document):

    # The table exists but the constraint was deferred.
    User._schema.create_foreign_key(User.favorite_tweet)

Note: SQLite does not support adding foreign key constraints to existing tables. On SQLite, create_foreign_key will result in an OperationalError <#OperationalError>.

Truncating a table:

    User._schema.truncate_table()       # No cascade.
    User._schema.truncate_table(cascade=True)   # Postgresql only.

See also: SchemaManager <#SchemaManager> API reference.

  Schema Migrations

Peewee does not include a built-in migration system. For schema changes in an existing deployment - adding columns, dropping columns, renaming tables, modifying indexes - use one of the following approaches.

  Playhouse migrate module

The migrate <#migrate> module in playhouse provides a set of helper functions for common schema changes, applied through a SchemaMigrator:

    from playhouse.migrate import *
    db = SqliteDatabase(...)
    migrator = SchemaMigrator.from_database(db)
    first_name = TextField(default='')
    last_name  = TextField(default='')
    with db.atomic():
        migrate(
            migrator.add_column('person', 'first_name', first_name),
            migrator.add_column('person', 'last_name',  last_name),
            migrator.drop_column('person', 'name'),
        )

Supported operations:

•   Add, rename, or drop columns.

•   Make columns nullable or not nullable.

•   Change a column's type.

•   Rename a table.

•   Add or drop indexes and constraints.

•   Add or drop column default values.

    See also: Schema Migrations <#migrate> for in-depth examples and API reference.

  Raw SQL migrations

For changes the migrate module does not cover, execute ALTER TABLE statements directly:

    with db.atomic():
        db.execute_sql('ALTER TABLE tweet ADD COLUMN view_count INTEGER DEFAULT 0')

  SQLite limitations

SQLite has limited ALTER TABLE support. It supports ADD COLUMN and RENAME TABLE but not DROP COLUMN, RENAME COLUMN, or constraint changes in older versions (SQLite 3.35.0+ adds DROP COLUMN).

For more complex SQLite schema changes, the standard workaround is to:

1.  Create a new table with the desired schema.

2.  Copy data with INSERT INTO new_table SELECT ... FROM old_table.

3.  Drop the old table.

4.  Rename the new table.

    The playhouse migrate <#migrate> module transparently handles the above workaround for older SQLite versions.

  Introspecting an Existing Schema

Database.get_tables() <#Database.get_tables> returns the names of all tables in the database:

    db.get_tables()
    # ['user', 'tweet', 'favorite']

Database.get_columns() <#Database.get_columns> returns column metadata for a table as a list of ColumnMetadata <#ColumnMetadata> instances:

    for col in db.get_columns('tweet'):
        print(col.name, col.data_type, col.null)

Database.get_indexes() <#Database.get_indexes> returns index metadata as a list of IndexMetadata <#IndexMetadata> instances:

    for idx in db.get_indexes('user'):
        print(idx.name, idx.columns, idx.unique)

Database.get_foreign_keys() <#Database.get_foreign_keys> returns foreign key metadata as a list of ForeignKeyMetadata <#ForeignKeyMetadata> instances:

    for fk in db.get_foreign_keys('tweet'):
        print(fk.column, '->', fk.dest_table, fk.dest_column)

Database.get_views() <#Database.get_views> returns a list of views in the database as a list of ViewMetadata <#ViewMetadata> instances:

    for view in db.get_views():
        print(view.name, view.sql)

  Generating models from an existing database

The pwiz - Model Generator <#pwiz> command-line tool introspects an existing database and emits Python model definitions:

    python -m pwiz -e postgresql my_database > models.py
    python -m pwiz -e sqlite my_app.db > models.py

The generated models can be used directly or as a starting point for further customization.

  Async Support

Peewee is a synchronous library by design. Its core query execution path uses blocking DB-API 2.0 calls. The async support described here bridges that synchronous core to an asyncio event loop using greenlet.

When database I/O occurs inside a greenlet, control is yielded to the event loop until the driver completes the operation. From the perspective of your application code, queries look synchronous; from the event loop's perspective, they yield during I/O.

This means existing Peewee code - queries, transactions, ORM methods - runs unchanged inside async contexts. No query-by-query API changes are required.

Note: For applications that use Peewee with a purely synchronous framework (Flask, Django, Bottle, etc.), no async setup is needed. See Framework Integration <#framework-integration> for examples of using Peewee with various sync and async frameworks.

  Installation

Requires Python 3.8 or newer, plus greenlet and an async-compatible database driver:

    pip install peewee greenlet
    pip install aiosqlite     # SQLite
    pip install asyncpg       # Postgresql
    pip install aiomysql      # MySQL / MariaDB

Supported backends:
Database | Driver | Peewee class
SQLite | aiosqlite | AsyncSqliteDatabase
Postgresql | asyncpg | AsyncPostgresqlDatabase
MySQL / MariaDB | aiomysql | AsyncMySQLDatabase

  Basic Usage

Import from playhouse.pwasyncio and use the async database class in place of the standard one. Models are defined identically:

    import asyncio
    from peewee import *
    from playhouse.pwasyncio import AsyncSqliteDatabase
    db = AsyncSqliteDatabase('my_app.db')
    class User(db.Model):
        name = TextField()

All queries must be executed through one of the async execution methods described below. The database context (async with db) acquires a connection from the pool and releases it on exit:

    async def main():
        async with db:
            await db.acreate_tables([User])
            user = await db.run(User.create, name='Charlie')
            charlie = await db.get(User.select().where(User.name == 'Charlie'))
            assert charlie.name == user.name
            for user in await db.list(User.select().order_by(User.name)):
                print(user.name)
        await db.close_pool()
    asyncio.run(main())

  Execution Methods

  db.run() - general-purpose entry point

run() accepts any callable and runs it inside a greenlet bridge. The callable can contain arbitrary synchronous Peewee code, including transactions:

    # Single operation:
    user = await db.run(User.create, name='Alice')
    # Multi-step function:
    def register(username, bio):
        with db.atomic():
            user = User.create(name=username)
            Profile.create(user=user, bio=bio)
            return user
    user = await db.run(register, 'alice', 'Python developer')

Use db.run() when:

•   You have existing synchronous code you want to call from async.

•   A single operation involves multiple queries (e.g. a transaction).

  Async Helper Methods

For single-query operations, the async helpers are more direct:

    # Execute any query and get its natural return type.
    cursor = await db.aexecute(query)
    # Use a transaction:
    async with db.atomic() as tx:
        await db.run(User.create, name='Bob')
    # SELECT and return one model instance (raises DoesNotExist if none).
    user = await db.get(User.select().where(User.name == 'Alice'))
    # SELECT and return a list.
    users = await db.list(User.select().order_by(User.name))
    # SELECT and return a scalar value.
    count = await db.scalar(User.select(fn.COUNT(User.id)))
    # CREATE TABLE / DROP TABLE:
    await db.acreate_tables([User, Tweet])
    await db.adrop_tables([User, Tweet])
    # Raw SQL:
    cursor = await db.aexecute_sql('SELECT 1')
    print(cursor.fetchall())   # [(1,)]

  Transactions

Use async with db.atomic() for async-aware transactions:

    async with db.atomic():
        await db.run(User.create, name='Alice')
        await db.run(User.create, name='Bob')
    # Both committed when the block exits.

Or wrap transactional code in db.run():

    def create_users():
        with db.atomic():
            User.create(name='Alice')
            User.create(name='Bob')
    await db.run(create_users)

Both approaches produce the same result. The db.run() form is often simpler when the transactional logic involves many inter-dependent queries.

  Connection Management

The database context manager (async with db) is the recommended way to manage connections. It acquires a connection on entry and releases it on exit:

    async with db:
        # Connection is available here.
        pass
    # Connection released.

Explicit control is also available:

    await db.aconnect()    # Acquire connection for the current task.
    # ... queries ...
    await db.aclose()      # Release connection back to pool.

Each asyncio task gets its own connection from the pool. Connections are not shared between tasks.

To shut down completely (e.g. during application teardown):

    await db.close_pool()

  SQLite

SQLite uses a single shared connection, as the underlying database does not support concurrent writers.

Generally SQLite is a poor fit for asynchronous workflows where writes may be coming in at any time, and transactions may be interleaved across multiple writers. Furthermore, SQLite does not do any network I/O.

The SQLite implementation is provided mostly for testing and local development.

  MySQL and Postgresql

MySQL and Postgresql use the driver's native connection pool.

Pool configuration options include:

•   pool_size - Maximum number of connections

•   pool_min_size - Minimum pool size

•   acquire_timeout - Timeout when acquiring a connection

        db = AsyncPostgresqlDatabase(
            'peewee_test',
            host='localhost',
            user='postgres',
            pool_size=10,
            pool_min_size=1,
            acquire_timeout=10)

  Sharp Corners

  Lazy foreign key access outside db.run()

Accessing a lazy foreign key attribute triggers a synchronous query. Outside a greenlet context, this raises MissingGreenletBridge:

    tweet = await db.get(Tweet.select())
    # FAILS: triggers a SELECT outside the greenlet bridge.
    print(tweet.user.name)
    # MissingGreenletBridge: Attempted query outside greenlet runner.

Fix by selecting the related model in the original query:

    query = Tweet.select(Tweet, User).join(User)
    tweet = await db.get(query)
    print(tweet.user.name)   # OK - no extra query.

Or by wrapping the access in db.run():

    name = await db.run(lambda: tweet.user.name)

To prevent errors from occurring at run-time you can disable lazy-loading on your foreign-key fields:

    class Tweet(db.Model):
        user = ForeignKeyField(User, backref='tweets', lazy_load=False)
        ...

  Iterating back-references outside db.run()

For the same reason, iterating a back-reference outside a greenlet context also fails:

    # FAILS:
    for tweet in user.tweets:
        print(tweet.content)

Solutions:

    # Using db.list():
    for tweet in await db.list(user.tweets):
        print(tweet.content)
    # Using db.run() with list():
    tweets = await db.run(list, user.tweets)
    # Using prefetch inside db.run():
    def get_user_with_tweets(user_id):
        user_q = User.select().where(User.id == user_id)
        return prefetch(user_q, Tweet.select())[0]
    user = await db.run(get_user_with_tweets, user_id)
    for tweet in user.tweets:   # Prefetched - no extra query.
        print(tweet.content)

The general rule is: any code that triggers a database query must execute inside a greenlet context, which means inside db.run() or an async helper call.

  API Reference

class AsyncDatabaseMixin(database, pool_size=10, pool_min_size=1, acquire_timeout=10, **kwargs)

Parameters

•   database (str) -- Database name or filename for SQLite.

•   pool_size (int) -- Maximum size of the driver-managed connection pool (no-op for SQLite).

•   pool_min_size (int) -- Minimum size of the driver-managed connection pool (no-op for SQLite).

•   acquire_timeout (float) -- Time (in seconds) to wait for a free connection when acquiring from the pool.

•   kwargs -- Arbitrary keyword arguments passed to the underlying database driver when creating connections (e.g., user, password, host).

    Mixin class providing asyncio execution support. Not used directly - instantiate AsyncSqliteDatabase, AsyncPostgresqlDatabase, or AsyncMySQLDatabase.

    Each asyncio task maintains its own connection state. Connections are acquired and released back to the pool when the task completes or the database context exits.

async run(fn, *args, **kwargs)

Parameters
    fn -- A synchronous callable.

Returns
    The return value of fn.

    Execute a synchronous callable inside a greenlet and return the result. This is the primary entry point for executing Peewee ORM code in an async context.

async aconnect()

Returns
    A wrapped async connection.

    Acquire a connection from the pool for the current task.

async aclose()
    Release the current task's connection back to the pool.

async close_pool()
    Close the underlying connection pool and release all active connections.

    This method should be called during application shutdown.

async __aenter__()
    Enter an async database context, acquiring a connection.

async __aexit__(exc_type, exc, tb)
    Exit the async database context, releasing the connection.

async aexecute(query)

Parameters
    query (Query <#Query>) -- a Select, Insert, Update or Delete query.

Returns
    the normal return-value for the query type.

    Execute any Peewee query object and return its natural result.

async get(query)

Parameters
    query (Query <#Query>) -- a Select query.

    Execute a SELECT query and return a single model instance. Raises DoesNotExist if no row matches.

async list(query)

Parameters
    query (Query <#Query>) -- a Select query, or an Insert, Update or Delete query that utilizes RETURNING.

    Execute a SELECT (or INSERT/UPDATE/DELETE with RETURNING) and return a list of results.

async scalar(query)

Parameters
    query (Query <#Query>) -- a Select query.

    Execute a SELECT and return the first column of the first row.

atomic()
    Return an async-aware atomic context manager. Supports both async with and with.

async acreate_tables(models, **options)
    Create tables asynchronously.

async adrop_tables(models, **options)
    Drop tables asynchronously.

async aexecute_sql(sql, params=None)

Parameters

•   sql (str) -- SQL query to execute.

•   params (tuple) -- Optional query parameters.

Returns
    A CursorAdapter instance.

    Execute raw SQL asynchronously. Returns a cursor-like object whose rows are already fetched (call .fetchall() synchronously).

class AsyncSqliteDatabase(database, **kwargs)
    Async SQLite database implementation.

    Uses aiosqlite and maintains a single shared connection. Pool-related configuration options are ignored.

    Inherits from AsyncDatabaseMixin and SqliteDatabase <#SqliteDatabase>.

class AsyncPostgresqlDatabase(database, **kwargs)
    Async Postgresql database implementation.

    Uses asyncpg and the driver's native connection pool.

    Inherits from AsyncDatabaseMixin and PostgresqlDatabase <#PostgresqlDatabase>.

class AsyncMySQLDatabase(database, **kwargs)
    Async MySQL / MariaDB database implementation.

    Uses aiomysql and the driver's native connection pool.

    Inherits from AsyncDatabaseMixin and MySQLDatabase <#MySQLDatabase>.

class MissingGreenletBridge(RuntimeError)
    Raised when Peewee attempts to execute a query outside a greenlet context. This indicates that a query was triggered outside of db.run() or an async helper call.

  Framework Integration

For web applications, it is common to open a connection when a request is received, and to close the connection when the response is delivered. This document describes how to add hooks to your web app to ensure the database connection is handled properly.

These steps will ensure that regardless of whether you're using a simple SqliteDatabase <#SqliteDatabase> or a PooledPostgresqlDatabase, peewee will handle the connections correctly.

The pattern is always the same:

    # On request start:
    db.connect()
    # On request end (success or error):
    if not db.is_closed():
        db.close()

Every framework exposes hooks for this. The sections below show the idiomatic approach for each.

Note: Applications that handle significant traffic should use a connection pool <#connection-pooling> to avoid the overhead of establishing a new connection per request. Pooled databases can be used as drop-in replacements for their non-pooled counterparts.

  Flask

Use before_request and teardown_request:

    from flask import Flask
    from peewee import *
    db = SqliteDatabase('my_app.db')
    app = Flask(__name__)
    @app.before_request
    def _db_connect():
        db.connect()
    @app.teardown_request
    def _db_close(exc):
        if not db.is_closed():
            db.close()

teardown_request is called regardless of whether the request succeeded or raised an exception, making it the correct hook for cleanup.

For a complete Flask + Peewee application example, see Example app <#example>.

See also: Flask Utilities <#flask-utils>

  FastAPI

FastAPI is an async framework and can be used with Peewee's Async Support <#asyncio> integration.

The following example demonstrates how to:

•   Ensure connection is opened and closed for each request.

•   Create tables/resources when app server starts.

•   Shut-down connection pool when app server exits.

        from fastapi import FastAPI
        from peewee import *
        from playhouse.pwasyncio import *
        app = FastAPI()
        db = AsyncPostgresqlDatabase('peewee_test', host='10.8.0.1', user='postgres')
        @app.middleware('http')
        async def database_connection(request, call_next):
            await db.aconnect()  # Obtain connection from connection pool.
            try:
                response = await call_next(request)
            finally:
                await db.aclose()  # Release connection back to pool.
            return response
        @app.on_event('startup')
        async def on_startup():
            async with db:
                await db.acreate_tables([Model1, Model2, Model3, ...])
        @app.on_event('shutdown')
        async def on_shutdown():
            await db.close_pool()

    Example demonstrating executing an async query:

        @app.get('/users')
        async def list_users():
            return await db.list(User.select().dicts())
        @app.post('/users')
        async def create_user(name: str):
            user = await db.run(User.create, name=name)
            return {'id': user.id, 'name': user.name}

    See also: Async Support <#asyncio>

  Django

Add a middleware that opens the connection before the view runs and closes it after the response is prepared. Place it first in MIDDLEWARE so it wraps all other middleware:

    # myproject/middleware.py
    from myproject.db import database
    def PeeweeConnectionMiddleware(get_response):
        def middleware(request):
            database.connect()
            try:
                response = get_response(request)
            finally:
                if not database.is_closed():
                    database.close()
            return response
        return middleware

    # settings.py
    MIDDLEWARE = [
        'myproject.middleware.PeeweeConnectionMiddleware',
        # ... rest of middleware ...
    ]

  Bottle

Use the before_request and after_request hooks:

    from bottle import hook
    from peewee import *
    db = SqliteDatabase('my_app.db')
    @hook('before_request')
    def _connect_db():
        db.connect()
    @hook('after_request')
    def _close_db():
        if not db.is_closed():
            db.close()

  Falcon

Add a middleware component:

    import falcon
    from peewee import *
    db = SqliteDatabase('my_app.db')
    class DatabaseMiddleware:
        def process_request(self, req, resp):
            db.connect()
        def process_response(self, req, resp, resource, req_succeeded):
            if not db.is_closed():
                db.close()
    app = falcon.App(middleware=[DatabaseMiddleware()])

  Pyramid

Set up a custom Request factory:

    from pyramid.request import Request
    from peewee import *
    db = SqliteDatabase('my_app.db')
    class MyRequest(Request):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            db.connect()
            self.add_finished_callback(self._close_db)
        def _close_db(self, request):
            if not db.is_closed():
                db.close()
    # In your application factory:
    def main(global_settings, **settings):
        config = Configurator(settings=settings)
        config.set_request_factory(MyRequest)

  Sanic

Sanic is an async framework and can be used with Peewee's Async Support <#asyncio> integration.

    from sanic import Sanic
    from peewee import *
    from playhouse.pwasyncio import *
    app = Sanic('PeeweeApp')
    db = AsyncPostgresqlDatabase('peewee_test', host='10.8.0.1', user='postgres')
    @app.on_request
    async def open_connection(request):
        await db.aconnect()  # Obtain connection from connection pool.
    @app.on_response
    async def close_connection(request, response):
        await db.aclose()  # Return connection to pool.
    @app.before_server_start
    async def setup_db(app):
        async with db:
            await db.acreate_tables([Model1, Model2, Model3, ...])
    @app.before_server_stop
    async def shutdown_db(app):
        await db.close_pool()

Example demonstrating executing an async query:

    from sanic import json
    @app.get('/message/')
    async def message(request):
        # Get the latest message from the database.
        message = await db.get(Message.select().order_by(Message.id.desc()))
        return json({'content': message.content, 'id': message.id})

See also: Async Support <#asyncio>

  CherryPy

Subscribe to the engine's before/after request events:

    import cherrypy
    from peewee import *
    db = SqliteDatabase('my_app.db')
    def _db_connect():
        db.connect()
    def _db_close():
        if not db.is_closed():
            db.close()
    cherrypy.engine.subscribe('before_request', _db_connect)
    cherrypy.engine.subscribe('after_request', _db_close)

  General Pattern for Any Framework

If your framework is not listed here, the integration follows the same structure:

1.  Find the hook that runs before every request handler.

2.  Call db.connect() there.

3.  Find the hook that runs after every request (success and error both).

4.  Call db.close() there if the connection is open.

    Any WSGI or ASGI middleware that wraps the application callable can also manage this:

        class PeeweeMiddleware:
            def __init__(self, app, database):
                self.app = app
                self.db = database
            def __call__(self, environ, start_response):
                self.db.connect()
                try:
                    return self.app(environ, start_response)
                finally:
                    if not self.db.is_closed():
                        self.db.close()
        # Wrap your WSGI app:
        application = PeeweeMiddleware(application, db)

  Using Peewee Interactively

Peewee contains helpers for working interactively from a Python interpreter or something like a Jupyter notebook. For this example, we'll assume that we have a pre-existing Sqlite database with the following simple schema:

    CREATE TABLE IF NOT EXISTS "event" (
        "id" INTEGER NOT NULL PRIMARY KEY,
        "key" TEXT NOT NULL,
        "timestamp" DATETIME NOT NULL,
        "metadata" TEXT NOT NULL);

To experiment with querying this database from an interactive interpreter session, we would start our interpreter and import the following helpers:

•   SqliteDatabase <#SqliteDatabase> - to reference the "events.db"

•   playhouse.reflection.generate_models() <#playhouse.reflection.generate_models> - to generate models from an existing database.

•   playhouse.reflection.print_model() <#playhouse.reflection.print_model> - to view the model definition.

•   playhouse.reflection.print_table_sql() <#playhouse.reflection.print_table_sql> - to view the table SQL.

    Our terminal session might look like this:

        >>> from peewee import SqliteDatabase
        >>> from playhouse.reflection import generate_models, print_model, print_table_sql
        >>>

    The generate_models() <#playhouse.reflection.generate_models> function will introspect the database and generate model classes for all the tables that are found. This is a handy way to get started and can save a lot of typing. The function returns a dictionary keyed by the table name, with the generated model as the corresponding value:

        >>> db = SqliteDatabase('events.db')
        >>> models = generate_models(db)
        >>> list(models.items())
        [('events', <Model: event>)]
        >>> globals().update(models)  # Inject models into global namespace.
        >>> event
        <Model: event>

    To take a look at the model definition, which lists the model's fields and data-type, we can use the print_model() <#playhouse.reflection.print_model> function:

        >>> print_model(event)
        event
          id AUTO
          key TEXT
          timestamp DATETIME
          metadata TEXT

    We can also generate a SQL CREATE TABLE for the introspected model, if you find that easier to read. This should match the actual table definition in the introspected database:

        >>> print_table_sql(event)
        CREATE TABLE IF NOT EXISTS "event" (
          "id" INTEGER NOT NULL PRIMARY KEY,
          "key" TEXT NOT NULL,
          "timestamp" DATETIME NOT NULL,
          "metadata" TEXT NOT NULL)

    Now that we are familiar with the structure of the table we're working with, we can run some queries on the generated event model:

        >>> for e in event.select().order_by(event.timestamp).limit(5):
        ...     print(e.key, e.timestamp)
        ...
        e00 2019-01-01 00:01:00
        e01 2019-01-01 00:02:00
        e02 2019-01-01 00:03:00
        e03 2019-01-01 00:04:00
        e04 2019-01-01 00:05:00
        >>> event.select(fn.MIN(event.timestamp), fn.MAX(event.timestamp)).scalar(as_tuple=True)
        (datetime.datetime(2019, 1, 1, 0, 1), datetime.datetime(2019, 1, 1, 1, 0))
        >>> event.select().count()  # Or, len(event)
        60

    For more information about these APIs and other similar reflection utilities, see the Reflection <#reflection> documentation.

    To generate an actual Python module containing model definitions for an existing database, you can use the command-line pwiz <#pwiz> tool. Here is a quick example:

        pwiz -e sqlite events.db > events.py

    The events.py file will now be an import-able module containing a database instance (referencing the events.db) along with model definitions for any tables found in the database. pwiz does some additional nice things like introspecting indexes and adding proper flags for NULL/NOT NULL constraints, etc.

    The APIs discussed in this section:

•   generate_models() <#playhouse.reflection.generate_models>

•   print_model() <#playhouse.reflection.print_model>

•   print_table_sql() <#playhouse.reflection.print_table_sql>

    More low-level APIs are also available on the Database <#Database> instance:

•   Database.get_tables() <#Database.get_tables>

•   Database.get_indexes() <#Database.get_indexes>

•   Database.get_columns() <#Database.get_columns> (for a given table)

•   Database.get_primary_keys() <#Database.get_primary_keys> (for a given table)

•   Database.get_foreign_keys() <#Database.get_foreign_keys> (for a given table)

  Query Builder

Peewee's high-level Model <#Model> and Field <#Field> APIs are built upon lower-level Table <#Table> and Column <#Column> counterparts. While these lower-level APIs are not documented in as much detail as their high-level counterparts, this document will present an overview with examples that should hopefully allow you to experiment.

We'll use the following schema:

    CREATE TABLE "person" (
        "id" INTEGER NOT NULL PRIMARY KEY,
        "first" TEXT NOT NULL,
        "last" TEXT NOT NULL);
    CREATE TABLE "note" (
        "id" INTEGER NOT NULL PRIMARY KEY,
        "person_id" INTEGER NOT NULL,
        "content" TEXT NOT NULL,
        "timestamp" DATETIME NOT NULL,
        FOREIGN KEY ("person_id") REFERENCES "person" ("id"));
    CREATE TABLE "reminder" (
        "id" INTEGER NOT NULL PRIMARY KEY,
        "note_id" INTEGER NOT NULL,
        "alarm" DATETIME NOT NULL,
        FOREIGN KEY ("note_id") REFERENCES "note" ("id"));

  Declaring tables

There are two ways we can declare Table <#Table> objects for working with these tables:

    # Explicitly declare columns
    Person = Table('person', ('id', 'first', 'last'))
    Note = Table('note', ('id', 'person_id', 'content', 'timestamp'))
    # Do not declare columns, they will be accessed using magic ".c" attribute
    Reminder = Table('reminder')

Typically we will want to bind() <#Table.bind> our tables to a database. This saves us having to pass the database explicitly every time we wish to execute a query on the table:

    db = SqliteDatabase('my_app.db')
    Person = Person.bind(db)
    Note = Note.bind(db)
    Reminder = Reminder.bind(db)

  Select queries

To select the first three notes and print their content, we can write:

    query = Note.select().order_by(Note.timestamp).limit(3)
    for note_dict in query:
        print(note_dict['content'])

Note: By default, rows will be returned as dictionaries. You can use the tuples() <#BaseQuery.tuples>, namedtuples() <#BaseQuery.namedtuples> or objects() <#BaseQuery.objects> methods to specify a different container for the row data, if you wish.

Because we didn't specify any columns, all the columns we defined in the note's Table <#Table> constructor will be selected. This won't work for Reminder, as we didn't specify any columns at all.

To select all notes published in 2018 along with the name of the creator, we will use join() <#Select.join>. We'll also request that rows be returned as namedtuple objects:

    query = (Note
             .select(Note.content, Note.timestamp, Person.first, Person.last)
             .join(Person, on=(Note.person_id == Person.id))
             .where(Note.timestamp >= datetime.date(2018, 1, 1))
             .order_by(Note.timestamp)
             .namedtuples())
    for row in query:
        print(row.timestamp, '-', row.content, '-', row.first, row.last)

Let's query for the most prolific people, that is, get the people who have created the most notes. This introduces calling a SQL function (COUNT), which is accomplished using the fn object:

    name = Person.first.concat(' ').concat(Person.last)
    query = (Person
             .select(name.alias('name'), fn.COUNT(Note.id).alias('count'))
             .join(Note, JOIN.LEFT_OUTER, on=(Note.person_id == Person.id))
             .group_by(name)
             .order_by(fn.COUNT(Note.id).desc()))
    for row in query:
        print(row['name'], row['count'])

There are a couple things to note in the above query:

•   We store an expression in a variable (name), then use it in the query.

•   We call SQL functions using fn.<function>(...) passing arguments as if it were a normal Python function.

•   The alias() <#ColumnBase.alias> method is used to specify the name used for a column or calculation.

    As a more complex example, we'll generate a list of all people and the contents and timestamp of their most recently-published note. To do this, we will end up using the Note table twice in different contexts within the same query, which will require us to use a table alias.

        # Start with the query that calculates the timestamp of the most recent
        # note for each person.
        NA = Note.alias('na')
        max_note = (NA
                    .select(NA.person_id, fn.MAX(NA.timestamp).alias('max_ts'))
                    .group_by(NA.person_id)
                    .alias('max_note'))
        # Now we'll select from the note table, joining on both the subquery and
        # on the person table to construct the result set.
        query = (Note
                 .select(Note.content, Note.timestamp, Person.first, Person.last)
                 .join(max_note, on=((max_note.c.person_id == Note.person_id) &
                                     (max_note.c.max_ts == Note.timestamp)))
                 .join(Person, on=(Note.person_id == Person.id))
                 .order_by(Person.first, Person.last))
        for row in query.namedtuples():
            print(row.first, row.last, ':', row.timestamp, '-', row.content)

    In the join predicate for the join on the max_note subquery, we can reference columns in the subquery using the magical ".c" attribute. So, max_note.c.max_ts is translated into "the max_ts column value from the max_note subquery".

    We can also use the ".c" magic attribute to access columns on tables that do not explicitly define their columns, like we did with the Reminder table. Here's a simple query to get all reminders for today, along with their associated note content:

        today = datetime.date.today()
        tomorrow = today + datetime.timedelta(days=1)
        query = (Reminder
                 .select(Reminder.c.alarm, Note.content)
                 .join(Note, on=(Reminder.c.note_id == Note.id))
                 .where(Reminder.c.alarm.between(today, tomorrow))
                 .order_by(Reminder.c.alarm))
        for row in query:
            print(row['alarm'], row['content'])

    Note: The ".c" attribute will not work on tables that explicitly define their columns, to prevent confusion.

  Insert Queries

Inserting data is straightforward. We can specify data to insert() <#Table.insert> in two different ways (in both cases, the ID of the new row is returned):

    # Using keyword arguments:
    zaizee_id = Person.insert(first='zaizee', last='cat').execute()
    # Using column: value mappings:
    Note.insert({
        Note.person_id: zaizee_id,
        Note.content: 'meeeeowwww',
        Note.timestamp: datetime.datetime.now()}).execute()

It is easy to bulk-insert data, just pass in either:

•   A list of dictionaries (all must have the same keys/columns).

•   A list of tuples, if the columns are specified explicitly.

    Examples:

        people = [
            {'first': 'Bob', 'last': 'Foo'},
            {'first': 'Herb', 'last': 'Bar'},
            {'first': 'Nuggie', 'last': 'Bar'}]
        # Inserting multiple rows returns the ID of the last-inserted row.
        last_id = Person.insert(people).execute()
        # We can also specify row tuples, so long as we tell Peewee which
        # columns the tuple values correspond to:
        people = [
            ('Bob', 'Foo'),
            ('Herb', 'Bar'),
            ('Nuggie', 'Bar')]
        Person.insert(people, columns=[Person.first, Person.last]).execute()

  Update Queries

update() <#Table.update> queries accept either keyword arguments or a dictionary mapping column to value, just like insert() <#Table.insert>.

Examples:

    # "Bob" changed his last name from "Foo" to "Baze".
    nrows = (Person
             .update(last='Baze')
             .where((Person.first == 'Bob') &
                    (Person.last == 'Foo'))
             .execute())
    # Use dictionary mapping column to value.
    nrows = (Person
             .update({Person.last: 'Baze'})
             .where((Person.first == 'Bob') &
                    (Person.last == 'Foo'))
             .execute())

You can also use expressions as the value to perform an atomic update. Imagine we have a PageView table and we need to atomically increment the page-view count for some URL:

    # Do an atomic update:
    (PageView
     .update({PageView.count: PageView.count + 1})
     .where(PageView.url == some_url)
     .execute())

  Delete Queries

delete() <#Table.delete> queries are simplest of all, as they do not accept any arguments:

    # Delete all notes created before 2018, returning number deleted.
    n = Note.delete().where(Note.timestamp < datetime.date(2018, 1, 1)).execute()

Because DELETE (and UPDATE) queries do not support joins, we can use subqueries to delete rows based on values in related tables. For example, here is how you would delete all notes by anyone whose last name is "Foo":

    # Get the id of all people whose last name is "Foo".
    foo_people = Person.select(Person.id).where(Person.last == 'Foo')
    # Delete all notes by any person whose ID is in the previous query.
    Note.delete().where(Note.person_id.in_(foo_people)).execute()

  Query Objects

One of the fundamental limitations of the abstractions provided by Peewee 2.x was the absence of a class that represented a structured query with no relation to a given model class.

An example of this might be computing aggregate values over a subquery. For example, the count() <#SelectBase.count> method, which returns the count of rows in an arbitrary query, is implemented by wrapping the query:

    SELECT COUNT(1) FROM (...)

To accomplish this with Peewee, the implementation is written in this way:

    def count(query):
        # Select([source1, ... sourcen], [column1, ...columnn])
        wrapped = Select(from_list=[query], columns=[fn.COUNT(SQL('1'))])
        curs = wrapped.tuples().execute(db)
        return curs[0][0]  # Return first column from first row of result.

We can actually express this more concisely using the scalar() <#SelectBase.scalar> method, which is suitable for returning values from aggregate queries:

    def count(query):
        wrapped = Select(from_list=[query], columns=[fn.COUNT(SQL('1'))])
        return wrapped.scalar(db)

The Query Library <#query-library> document has a more complex example, in which we write a query for a facility with the highest number of available slots booked:

The SQL we wish to express is:

    SELECT facid, total FROM (
      SELECT facid, SUM(slots) AS total,
             rank() OVER (order by SUM(slots) DESC) AS rank
      FROM bookings
      GROUP BY facid
    ) AS ranked
    WHERE rank = 1

We can express this fairly elegantly by using a plain Select <#Select> for the outer query:

    # Store rank expression in variable for readability.
    rank_expr = fn.rank().over(order_by=[fn.SUM(Booking.slots).desc()])
    subq = (Booking
            .select(Booking.facility, fn.SUM(Booking.slots).alias('total'),
                    rank_expr.alias('rank'))
            .group_by(Booking.facility))
    # Use a plain "Select" to create outer query.
    query = (Select(columns=[subq.c.facid, subq.c.total])
             .from_(subq)
             .where(subq.c.rank == 1)
             .tuples())
    # Iterate over the resulting facility ID(s) and total(s):
    for facid, total in query.execute(db):
        print(facid, total)

For another example, let's create a recursive common table expression to calculate the first 10 fibonacci numbers:

    base = Select(columns=(
        Value(1).alias('n'),
        Value(0).alias('fib_n'),
        Value(1).alias('next_fib_n'))).cte('fibonacci', recursive=True)
    n = (base.c.n + 1).alias('n')
    recursive_term = Select(columns=(
        n,
        base.c.next_fib_n,
        base.c.fib_n + base.c.next_fib_n)).from_(base).where(n < 10)
    fibonacci = base.union_all(recursive_term)
    query = fibonacci.select_from(fibonacci.c.n, fibonacci.c.fib_n)
    results = list(query.execute(db))
    # Generates the following result list:
    [{'fib_n': 0, 'n': 1},
     {'fib_n': 1, 'n': 2},
     {'fib_n': 1, 'n': 3},
     {'fib_n': 2, 'n': 4},
     {'fib_n': 3, 'n': 5},
     {'fib_n': 5, 'n': 6},
     {'fib_n': 8, 'n': 7},
     {'fib_n': 13, 'n': 8},
     {'fib_n': 21, 'n': 9},
     {'fib_n': 34, 'n': 10}]

  More

For a description of the various classes used to describe a SQL AST, see the query builder API documentation <#query-builder-api>.

If you're interested in learning more, you can also check out the project source code <https://github.com/coleifer/peewee>.

  Query Library

These query examples are taken from the site Postgresql Exercises <https://pgexercises.com/>. A sample data-set can be found on the getting started page <https://pgexercises.com/gettingstarted.html>.

Direct download: clubdata.sql <https://raw.githubusercontent.com/coleifer/peewee/refs/heads/master/docs/clubdata.sql>

  On this page

•   Model Definitions

•   Schema Creation

•   Basic Exercises

•   Joins and Subqueries

•   Modifying Data

•   Aggregation

•   Recursion

  Model Definitions

Here is a visual representation of the schema used in these examples: [image]

To begin working with the data, we'll define the model classes that correspond to the tables in the diagram.

Note: In some cases we explicitly specify column names for a particular field. This is so our models are compatible with the database schema used for the postgres exercises.

    from functools import partial
    from peewee import *
    db = PostgresqlDatabase('peewee_test')
    class BaseModel(Model):
        class Meta:
            database = db
    class Member(BaseModel):
        memid = AutoField()  # Auto-incrementing primary key.
        surname = CharField()
        firstname = CharField()
        address = CharField(max_length=300)
        zipcode = IntegerField()
        telephone = CharField()
        recommendedby = ForeignKeyField('self', backref='recommended',
                                        column_name='recommendedby', null=True)
        joindate = DateTimeField()
        class Meta:
            table_name = 'members'
    # Conveniently declare decimal fields suitable for storing currency.
    MoneyField = partial(DecimalField, decimal_places=2)
    class Facility(BaseModel):
        facid = AutoField()
        name = CharField()
        membercost = MoneyField()
        guestcost = MoneyField()
        initialoutlay = MoneyField()
        monthlymaintenance = MoneyField()
        class Meta:
            table_name = 'facilities'
    class Booking(BaseModel):
        bookid = AutoField()
        facility = ForeignKeyField(Facility, column_name='facid')
        member = ForeignKeyField(Member, column_name='memid')
        starttime = DateTimeField()
        slots = IntegerField()
        class Meta:
            table_name = 'bookings'

  Schema Creation

If you downloaded the SQL file from the Postgresql Exercises site, then you can load the data into a Postgresql database using the following commands:

    createdb peewee_test
    psql -U postgres -f clubdata.sql -d peewee_test -x -q

To create the schema using Peewee, without loading the sample data, you can run the following:

    # Assumes you have created the database "peewee_test" already.
    db.create_tables([Member, Facility, Booking])

  Basic Exercises

This category deals with the basics of SQL. It covers select and where clauses, case expressions, unions, and a few other odds and ends.

  Retrieve everything

Retrieve all information from facilities table.

    SELECT * FROM facilities

    # By default, when no fields are explicitly passed to select(), all fields
    # will be selected.
    query = Facility.select()

  Retrieve specific columns from a table

Retrieve names of facilities and cost to members.

    SELECT name, membercost FROM facilities;

    query = Facility.select(Facility.name, Facility.membercost)
    # To iterate:
    for facility in query:
        print(facility.name)

  Control which rows are retrieved

Retrieve list of facilities that have a cost to members.

    SELECT * FROM facilities WHERE membercost > 0

    query = Facility.select().where(Facility.membercost > 0)

  Control which rows are retrieved - part 2

Retrieve list of facilities that have a cost to members, and that fee is less than 1/50th of the monthly maintenance cost. Return id, name, cost and monthly-maintenance.

    SELECT facid, name, membercost, monthlymaintenance
    FROM facilities
    WHERE membercost > 0 AND membercost < (monthlymaintenance / 50)

    query = (Facility
             .select(Facility.facid, Facility.name, Facility.membercost,
                     Facility.monthlymaintenance)
             .where(
                 (Facility.membercost > 0) &
                 (Facility.membercost < (Facility.monthlymaintenance / 50))))

  Basic string searches

How can you produce a list of all facilities with the word 'Tennis' in their name?

    SELECT * FROM facilities WHERE name ILIKE '%tennis%';

    query = Facility.select().where(Facility.name.contains('tennis'))
    # OR use the exponent operator. Note: you must include wildcards here:
    query = Facility.select().where(Facility.name ** '%tennis%')

  Matching against multiple possible values

How can you retrieve the details of facilities with ID 1 and 5? Try to do it without using the OR operator.

    SELECT * FROM facilities WHERE facid IN (1, 5);

    query = Facility.select().where(Facility.facid.in_([1, 5]))
    # OR:
    query = Facility.select().where((Facility.facid == 1) |
                                    (Facility.facid == 5))

  Classify results into buckets

How can you produce a list of facilities, with each labelled as 'cheap' or 'expensive' depending on if their monthly maintenance cost is more than $100? Return the name and monthly maintenance of the facilities in question.

    SELECT name,
    CASE WHEN monthlymaintenance > 100 THEN 'expensive' ELSE 'cheap' END
    FROM facilities;

    cost = Case(None, [(Facility.monthlymaintenance > 100, 'expensive')], 'cheap')
    query = Facility.select(Facility.name, cost.alias('cost'))

Note: See documentation Case <#Case> for more examples.

  Working with dates

How can you produce a list of members who joined after the start of September 2012? Return the memid, surname, firstname, and joindate of the members in question.

    SELECT memid, surname, firstname, joindate FROM members
    WHERE joindate >= '2012-09-01';

    query = (Member
             .select(Member.memid, Member.surname, Member.firstname, Member.joindate)
             .where(Member.joindate >= datetime.date(2012, 9, 1)))

  Removing duplicates, and ordering results

How can you produce an ordered list of the first 10 surnames in the members table? The list must not contain duplicates.

    SELECT DISTINCT surname FROM members ORDER BY surname LIMIT 10;

    query = (Member
             .select(Member.surname)
             .order_by(Member.surname)
             .limit(10)
             .distinct())

  Combining results from multiple queries

You, for some reason, want a combined list of all surnames and all facility names.

    SELECT surname FROM members UNION SELECT name FROM facilities;

    lhs = Member.select(Member.surname)
    rhs = Facility.select(Facility.name)
    query = lhs | rhs

Queries can be composed using the following operators:

•   | - UNION

•   + - UNION ALL

•   & - INTERSECT

•   - - EXCEPT

  Simple aggregation

You'd like to get the signup date of your last member. How can you retrieve this information?

    SELECT MAX(join_date) FROM members;

    query = Member.select(fn.MAX(Member.joindate))
    # To conveniently obtain a single scalar value, use "scalar()":
    # max_join_date = query.scalar()

  More aggregation

You'd like to get the first and last name of the last member(s) who signed up - not just the date.

    SELECT firstname, surname, joindate FROM members
    WHERE joindate = (SELECT MAX(joindate) FROM members);

    # Use "alias()" to reference the same table multiple times in a query.
    MemberAlias = Member.alias()
    subq = MemberAlias.select(fn.MAX(MemberAlias.joindate))
    query = (Member
             .select(Member.firstname, Member.surname, Member.joindate)
             .where(Member.joindate == subq))

  Joins and Subqueries

This category deals primarily with a foundational concept in relational database systems: joining. Joining allows you to combine related information from multiple tables to answer a question. This isn't just beneficial for ease of querying: a lack of join capability encourages denormalisation of data, which increases the complexity of keeping your data internally consistent.

This topic covers inner, outer, and self joins, as well as spending a little time on subqueries (queries within queries).

  Retrieve the start times of members' bookings

How can you produce a list of the start times for bookings by members named 'David Farrell'?

    SELECT starttime FROM bookings
    INNER JOIN members ON (bookings.memid = members.memid)
    WHERE surname = 'Farrell' AND firstname = 'David';

    query = (Booking
             .select(Booking.starttime)
             .join(Member)
             .where((Member.surname == 'Farrell') &
                    (Member.firstname == 'David')))

  Work out the start times of bookings for tennis courts

How can you produce a list of the start times for bookings for tennis courts, for the date '2012-09-21'? Return a list of start time and facility name pairings, ordered by the time.

    SELECT starttime, name
    FROM bookings
    INNER JOIN facilities ON (bookings.facid = facilities.facid)
    WHERE date_trunc('day', starttime) = '2012-09-21':: date
      AND name ILIKE 'tennis%'
    ORDER BY starttime, name;

    query = (Booking
             .select(Booking.starttime, Facility.name)
             .join(Facility)
             .where(
                 (fn.date_trunc('day', Booking.starttime) == datetime.date(2012, 9, 21)) &
                 Facility.name.startswith('Tennis'))
             .order_by(Booking.starttime, Facility.name))
    # To retrieve the joined facility's name when iterating:
    for booking in query:
        print(booking.starttime, booking.facility.name)

  Produce a list of all members who have recommended another member

How can you output a list of all members who have recommended another member? Ensure that there are no duplicates in the list, and that results are ordered by (surname, firstname).

    SELECT DISTINCT m.firstname, m.surname
    FROM members AS m2
    INNER JOIN members AS m ON (m.memid = m2.recommendedby)
    ORDER BY m.surname, m.firstname;

    MA = Member.alias()
    query = (Member
             .select(Member.firstname, Member.surname)
             .join(MA, on=(MA.recommendedby == Member.memid))
             .order_by(Member.surname, Member.firstname))

  Produce a list of all members, along with their recommender

How can you output a list of all members, including the individual who recommended them (if any)? Ensure that results are ordered by (surname, firstname).

    SELECT m.firstname, m.surname, r.firstname, r.surname
    FROM members AS m
    LEFT OUTER JOIN members AS r ON (m.recommendedby = r.memid)
    ORDER BY m.surname, m.firstname

    MA = Member.alias()
    query = (Member
             .select(Member.firstname, Member.surname, MA.firstname, MA.surname)
             .join(MA, JOIN.LEFT_OUTER, on=(Member.recommendedby == MA.memid))
             .order_by(Member.surname, Member.firstname))
    # To display the recommender's name when iterating:
    for m in query:
        print(m.firstname, m.surname)
        if m.recommendedby:
            print('  ', m.recommendedby.firstname, m.recommendedby.surname)

  Produce a list of all members who have used a tennis court

How can you produce a list of all members who have used a tennis court? Include in your output the name of the court, and the name of the member formatted as a single column. Ensure no duplicate data, and order by the member name.

    SELECT DISTINCT m.firstname || ' ' || m.surname AS member, f.name AS facility
    FROM members AS m
    INNER JOIN bookings AS b ON (m.memid = b.memid)
    INNER JOIN facilities AS f ON (b.facid = f.facid)
    WHERE f.name LIKE 'Tennis%'
    ORDER BY member, facility;

    fullname = Member.firstname + ' ' + Member.surname
    query = (Member
             .select(fullname.alias('member'), Facility.name.alias('facility'))
             .join(Booking)
             .join(Facility)
             .where(Facility.name.startswith('Tennis'))
             .order_by(fullname, Facility.name)
             .distinct())

  Produce a list of costly bookings

How can you produce a list of bookings on the day of 2012-09-14 which will cost the member (or guest) more than $30? Remember that guests have different costs to members (the listed costs are per half-hour 'slot'), and the guest user is always ID 0. Include in your output the name of the facility, the name of the member formatted as a single column, and the cost. Order by descending cost, and do not use any subqueries.

    SELECT m.firstname || ' ' || m.surname AS member,
           f.name AS facility,
           (CASE WHEN m.memid = 0 THEN f.guestcost * b.slots
            ELSE f.membercost * b.slots END) AS cost
    FROM members AS m
    INNER JOIN bookings AS b ON (m.memid = b.memid)
    INNER JOIN facilities AS f ON (b.facid = f.facid)
    WHERE (date_trunc('day', b.starttime) = '2012-09-14') AND
     ((m.memid = 0 AND b.slots * f.guestcost > 30) OR
      (m.memid > 0 AND b.slots * f.membercost > 30))
    ORDER BY cost DESC;

    cost = Case(Member.memid, (
        (0, Booking.slots * Facility.guestcost),
    ), (Booking.slots * Facility.membercost))
    fullname = Member.firstname + ' ' + Member.surname
    query = (Member
             .select(fullname.alias('member'), Facility.name.alias('facility'),
                     cost.alias('cost'))
             .join(Booking)
             .join(Facility)
             .where(
                 (fn.date_trunc('day', Booking.starttime) == datetime.date(2012, 9, 14)) &
                 (cost > 30))
             .order_by(SQL('cost').desc()))
    # To iterate over the results, it might be easiest to use namedtuples:
    for row in query.namedtuples():
        print(row.member, row.facility, row.cost)

  Produce a list of all members, along with their recommender, using no joins.

How can you output a list of all members, including the individual who recommended them (if any), without using any joins? Ensure that there are no duplicates in the list, and that each firstname + surname pairing is formatted as a column and ordered.

    SELECT DISTINCT m.firstname || ' ' || m.surname AS member,
       (SELECT r.firstname || ' ' || r.surname
        FROM cd.members AS r
        WHERE m.recommendedby = r.memid) AS recommended
    FROM members AS m ORDER BY member;

    MA = Member.alias()
    subq = (MA
            .select(MA.firstname + ' ' + MA.surname)
            .where(Member.recommendedby == MA.memid))
    query = (Member
             .select(fullname.alias('member'), subq.alias('recommended'))
             .order_by(fullname))

  Produce a list of costly bookings, using a subquery

The "Produce a list of costly bookings" exercise contained some messy logic: we had to calculate the booking cost in both the WHERE clause and the CASE statement. Try to simplify this calculation using subqueries.

    SELECT member, facility, cost from (
      SELECT
      m.firstname || ' ' || m.surname as member,
      f.name as facility,
      CASE WHEN m.memid = 0 THEN b.slots * f.guestcost
      ELSE b.slots * f.membercost END AS cost
      FROM members AS m
      INNER JOIN bookings AS b ON m.memid = b.memid
      INNER JOIN facilities AS f ON b.facid = f.facid
      WHERE date_trunc('day', b.starttime) = '2012-09-14'
    ) as bookings
    WHERE cost > 30
    ORDER BY cost DESC;

    cost = Case(Member.memid, (
        (0, Booking.slots * Facility.guestcost),
    ), (Booking.slots * Facility.membercost))
    iq = (Member
          .select(fullname.alias('member'), Facility.name.alias('facility'),
                  cost.alias('cost'))
          .join(Booking)
          .join(Facility)
          .where(fn.date_trunc('day', Booking.starttime) == datetime.date(2012, 9, 14)))
    query = (Member
             .select(iq.c.member, iq.c.facility, iq.c.cost)
             .from_(iq)
             .where(iq.c.cost > 30)
             .order_by(SQL('cost').desc()))
    # To iterate, try using dicts:
    for row in query.dicts():
        print(row['member'], row['facility'], row['cost'])

  Modifying Data

Querying data is all well and good, but at some point you're probably going to want to put data into your database! This section deals with inserting, updating, and deleting information. Operations that alter your data like this are collectively known as Data Manipulation Language, or DML.

In previous sections, we returned to you the results of the query you've performed. Since modifications like the ones we're making in this section don't return any query results, we instead show you the updated content of the table you're supposed to be working on.

  Insert some data into a table

The club is adding a new facility - a spa. We need to add it into the facilities table. Use the following values: facid: 9, Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800

    INSERT INTO "facilities" ("facid", "name", "membercost", "guestcost",
    "initialoutlay", "monthlymaintenance") VALUES (9, 'Spa', 20, 30, 100000, 800)

    res = Facility.insert({
        Facility.facid: 9,
        Facility.name: 'Spa',
        Facility.membercost: 20,
        Facility.guestcost: 30,
        Facility.initialoutlay: 100000,
        Facility.monthlymaintenance: 800}).execute()
    # OR:
    res = (Facility
           .insert(facid=9, name='Spa', membercost=20, guestcost=30,
                   initialoutlay=100000, monthlymaintenance=800)
           .execute())

  Insert multiple rows of data into a table

In the previous exercise, you learned how to add a facility. Now you're going to add multiple facilities in one command. Use the following values:

facid: 9, Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.

facid: 10, Name: 'Squash Court 2', membercost: 3.5, guestcost: 17.5, initialoutlay: 5000, monthlymaintenance: 80.

    INSERT INTO "facilities" (...)
    VALUES (9, ...), (10, ...);

    data = [
        {'facid': 9, 'name': 'Spa', 'membercost': 20, 'guestcost': 30,
         'initialoutlay': 100000, 'monthlymaintenance': 800},
        {'facid': 10, 'name': 'Squash Court 2', 'membercost': 3.5,
         'guestcost': 17.5, 'initialoutlay': 5000, 'monthlymaintenance': 80}]
    res = Facility.insert_many(data).execute()

  Insert calculated data into a table

Let's try adding the spa to the facilities table again. This time, though, we want to automatically generate the value for the next facid, rather than specifying it as a constant. Use the following values for everything else: Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.

    INSERT INTO "facilities" ("facid", "name", "membercost", "guestcost",
      "initialoutlay", "monthlymaintenance")
    SELECT (SELECT (MAX("facid") + 1) FROM "facilities") AS _,
            'Spa', 20, 30, 100000, 800;

    maxq = Facility.select(fn.MAX(Facility.facid) + 1)
    subq = Select(columns=(maxq, 'Spa', 20, 30, 100000, 800))
    res = Facility.insert_from(subq, Facility._meta.sorted_fields).execute()

  Update some existing data

We made a mistake when entering the data for the second tennis court. The initial outlay was 10000 rather than 8000: you need to alter the data to fix the error.

    UPDATE facilities SET initialoutlay = 10000 WHERE name = 'Tennis Court 2';

    res = (Facility
           .update({Facility.initialoutlay: 10000})
           .where(Facility.name == 'Tennis Court 2')
           .execute())
    # OR:
    res = (Facility
           .update(initialoutlay=10000)
           .where(Facility.name == 'Tennis Court 2')
           .execute())

  Update multiple rows and columns at the same time

We want to increase the price of the tennis courts for both members and guests. Update the costs to be 6 for members, and 30 for guests.

    UPDATE facilities SET membercost=6, guestcost=30 WHERE name ILIKE 'Tennis%';

    nrows = (Facility
             .update(membercost=6, guestcost=30)
             .where(Facility.name.startswith('Tennis'))
             .execute())

  Update a row based on the contents of another row

We want to alter the price of the second tennis court so that it costs 10% more than the first one. Try to do this without using constant values for the prices, so that we can reuse the statement if we want to.

    UPDATE facilities SET
    membercost = (SELECT membercost * 1.1 FROM facilities WHERE facid = 0),
    guestcost = (SELECT guestcost * 1.1 FROM facilities WHERE facid = 0)
    WHERE facid = 1;
    -- OR --
    WITH new_prices (nmc, ngc) AS (
      SELECT membercost * 1.1, guestcost * 1.1
      FROM facilities WHERE name = 'Tennis Court 1')
    UPDATE facilities
    SET membercost = new_prices.nmc, guestcost = new_prices.ngc
    FROM new_prices
    WHERE name = 'Tennis Court 2'

    sq1 = Facility.select(Facility.membercost * 1.1).where(Facility.facid == 0)
    sq2 = Facility.select(Facility.guestcost * 1.1).where(Facility.facid == 0)
    res = (Facility
           .update(membercost=sq1, guestcost=sq2)
           .where(Facility.facid == 1)
           .execute())
    # OR:
    cte = (Facility
           .select(Facility.membercost * 1.1, Facility.guestcost * 1.1)
           .where(Facility.name == 'Tennis Court 1')
           .cte('new_prices', columns=('nmc', 'ngc')))
    res = (Facility
           .update(membercost=SQL('new_prices.nmc'), guestcost=SQL('new_prices.ngc'))
           .with_cte(cte)
           .from_(cte)
           .where(Facility.name == 'Tennis Court 2')
           .execute())

  Delete all bookings

As part of a clearout of our database, we want to delete all bookings from the bookings table.

    DELETE FROM bookings;

    nrows = Booking.delete().execute()

  Delete a member from the cd.members table

We want to remove member 37, who has never made a booking, from our database.

    DELETE FROM members WHERE memid = 37;

    nrows = Member.delete().where(Member.memid == 37).execute()

  Delete based on a subquery

How can we make that more general, to delete all members who have never made a booking?

    DELETE FROM members WHERE NOT EXISTS (
      SELECT * FROM bookings WHERE bookings.memid = members.memid);

    subq = Booking.select().where(Booking.member == Member.memid)
    nrows = Member.delete().where(~fn.EXISTS(subq)).execute()

  Aggregation

Aggregation is one of those capabilities that really make you appreciate the power of relational database systems. It allows you to move beyond merely persisting your data, into the realm of asking truly interesting questions that can be used to inform decision making. This category covers aggregation at length, making use of standard grouping as well as more recent window functions.

  Count the number of facilities

For our first foray into aggregates, we're going to stick to something simple. We want to know how many facilities exist - simply produce a total count.

    SELECT COUNT(facid) FROM facilities;

    query = Facility.select(fn.COUNT(Facility.facid))
    count = query.scalar()
    # OR:
    count = Facility.select().count()

  Count the number of expensive facilities

Produce a count of the number of facilities that have a cost to guests of 10 or more.

    SELECT COUNT(facid) FROM facilities WHERE guestcost >= 10

    query = Facility.select(fn.COUNT(Facility.facid)).where(Facility.guestcost >= 10)
    count = query.scalar()
    # OR:
    # count = Facility.select().where(Facility.guestcost >= 10).count()

  Count the number of recommendations each member makes.

Produce a count of the number of recommendations each member has made. Order by member ID.

    SELECT recommendedby, COUNT(memid) FROM members
    WHERE recommendedby IS NOT NULL
    GROUP BY recommendedby
    ORDER BY recommendedby

    query = (Member
             .select(Member.recommendedby, fn.COUNT(Member.memid))
             .where(Member.recommendedby.is_null(False))
             .group_by(Member.recommendedby)
             .order_by(Member.recommendedby))

  List the total slots booked per facility

Produce a list of the total number of slots booked per facility. For now, just produce an output table consisting of facility id and slots, sorted by facility id.

    SELECT facid, SUM(slots) FROM bookings GROUP BY facid ORDER BY facid;

    query = (Booking
             .select(Booking.facid, fn.SUM(Booking.slots))
             .group_by(Booking.facid)
             .order_by(Booking.facid))

  List the total slots booked per facility in a given month

Produce a list of the total number of slots booked per facility in the month of September 2012. Produce an output table consisting of facility id and slots, sorted by the number of slots.

    SELECT facid, SUM(slots)
    FROM bookings
    WHERE (date_trunc('month', starttime) = '2012-09-01'::dates)
    GROUP BY facid
    ORDER BY SUM(slots)

    query = (Booking
             .select(Booking.facility, fn.SUM(Booking.slots))
             .where(fn.date_trunc('month', Booking.starttime) == datetime.date(2012, 9, 1))
             .group_by(Booking.facility)
             .order_by(fn.SUM(Booking.slots)))

  List the total slots booked per facility per month

Produce a list of the total number of slots booked per facility per month in the year of 2012. Produce an output table consisting of facility id and slots, sorted by the id and month.

    SELECT facid, date_part('month', starttime), SUM(slots)
    FROM bookings
    WHERE date_part('year', starttime) = 2012
    GROUP BY facid, date_part('month', starttime)
    ORDER BY facid, date_part('month', starttime)

    month = fn.date_part('month', Booking.starttime)
    query = (Booking
             .select(Booking.facility, month, fn.SUM(Booking.slots))
             .where(fn.date_part('year', Booking.starttime) == 2012)
             .group_by(Booking.facility, month)
             .order_by(Booking.facility, month))

  Find the count of members who have made at least one booking

Find the total number of members who have made at least one booking.

    SELECT COUNT(DISTINCT memid) FROM bookings
    -- OR --
    SELECT COUNT(1) FROM (SELECT DISTINCT memid FROM bookings) AS _

    query = Booking.select(fn.COUNT(Booking.member.distinct()))
    # OR:
    query = Booking.select(Booking.member).distinct()
    count = query.count()  # count() wraps in SELECT COUNT(1) FROM (...)

  List facilities with more than 1000 slots booked

Produce a list of facilities with more than 1000 slots booked. Produce an output table consisting of facility id and hours, sorted by facility id.

    SELECT facid, SUM(slots) FROM bookings
    GROUP BY facid
    HAVING SUM(slots) > 1000
    ORDER BY facid;

    query = (Booking
             .select(Booking.facility, fn.SUM(Booking.slots))
             .group_by(Booking.facility)
             .having(fn.SUM(Booking.slots) > 1000)
             .order_by(Booking.facility))

  Find the total revenue of each facility

Produce a list of facilities along with their total revenue. The output table should consist of facility name and revenue, sorted by revenue. Remember that there's a different cost for guests and members!

    SELECT f.name, SUM(b.slots * (
    CASE WHEN b.memid = 0 THEN f.guestcost ELSE f.membercost END)) AS revenue
    FROM bookings AS b
    INNER JOIN facilities AS f ON b.facid = f.facid
    GROUP BY f.name
    ORDER BY revenue;

    revenue = fn.SUM(Booking.slots * Case(None, (
        (Booking.member == 0, Facility.guestcost),
    ), Facility.membercost))
    query = (Facility
             .select(Facility.name, revenue.alias('revenue'))
             .join(Booking)
             .group_by(Facility.name)
             .order_by(SQL('revenue')))

  Find facilities with a total revenue less than 1000

Produce a list of facilities with a total revenue less than 1000. Produce an output table consisting of facility name and revenue, sorted by revenue. Remember that there's a different cost for guests and members!

    SELECT f.name, SUM(b.slots * (
    CASE WHEN b.memid = 0 THEN f.guestcost ELSE f.membercost END)) AS revenue
    FROM bookings AS b
    INNER JOIN facilities AS f ON b.facid = f.facid
    GROUP BY f.name
    HAVING SUM(b.slots * ...) < 1000
    ORDER BY revenue;

    # Same definition as previous example.
    revenue = fn.SUM(Booking.slots * Case(None, (
        (Booking.member == 0, Facility.guestcost),
    ), Facility.membercost))
    query = (Facility
             .select(Facility.name, revenue.alias('revenue'))
             .join(Booking)
             .group_by(Facility.name)
             .having(revenue < 1000)
             .order_by(SQL('revenue')))

  Output the facility id that has the highest number of slots booked

Output the facility id that has the highest number of slots booked.

    SELECT facid, SUM(slots) FROM bookings
    GROUP BY facid
    ORDER BY SUM(slots) DESC
    LIMIT 1

    query = (Booking
             .select(Booking.facility, fn.SUM(Booking.slots))
             .group_by(Booking.facility)
             .order_by(fn.SUM(Booking.slots).desc())
             .limit(1))
    # Retrieve multiple scalar values by calling scalar() with as_tuple=True.
    facid, nslots = query.scalar(as_tuple=True)

  List the total slots booked per facility per month, part 2

Produce a list of the total number of slots booked per facility per month in the year of 2012. In this version, include output rows containing totals for all months per facility, and a total for all months for all facilities. The output table should consist of facility id, month and slots, sorted by the id and month. When calculating the aggregated values for all months and all facids, return null values in the month and facid columns.

Postgres ONLY.

    SELECT facid, date_part('month', starttime), SUM(slots)
    FROM booking
    WHERE date_part('year', starttime) = 2012
    GROUP BY ROLLUP(facid, date_part('month', starttime))
    ORDER BY facid, date_part('month', starttime)

    month = fn.date_part('month', Booking.starttime)
    query = (Booking
             .select(Booking.facility,
                     month.alias('month'),
                     fn.SUM(Booking.slots))
             .where(fn.date_part('year', Booking.starttime) == 2012)
             .group_by(fn.ROLLUP(Booking.facility, month))
             .order_by(Booking.facility, month))

  List the total hours booked per named facility

Produce a list of the total number of hours booked per facility, remembering that a slot lasts half an hour. The output table should consist of the facility id, name, and hours booked, sorted by facility id.

    SELECT f.facid, f.name, SUM(b.slots) * .5
    FROM facilities AS f
    INNER JOIN bookings AS b ON (f.facid = b.facid)
    GROUP BY f.facid, f.name
    ORDER BY f.facid

    query = (Facility
             .select(Facility.facid, Facility.name, fn.SUM(Booking.slots) * .5)
             .join(Booking)
             .group_by(Facility.facid, Facility.name)
             .order_by(Facility.facid))

  List each member's first booking after September 1st 2012

Produce a list of each member name, id, and their first booking after September 1st 2012. Order by member ID.

    SELECT m.surname, m.firstname, m.memid, min(b.starttime) as starttime
    FROM members AS m
    INNER JOIN bookings AS b ON b.memid = m.memid
    WHERE starttime >= '2012-09-01'
    GROUP BY m.surname, m.firstname, m.memid
    ORDER BY m.memid;

    query = (Member
             .select(Member.surname, Member.firstname, Member.memid,
                     fn.MIN(Booking.starttime).alias('starttime'))
             .join(Booking)
             .where(Booking.starttime >= datetime.date(2012, 9, 1))
             .group_by(Member.surname, Member.firstname, Member.memid)
             .order_by(Member.memid))

  Produce a list of member names, with each row containing the total member count

Produce a list of member names, with each row containing the total member count. Order by join date.

Postgres ONLY (as written).

    SELECT COUNT(*) OVER(), firstname, surname
    FROM members ORDER BY joindate

    query = (Member
             .select(fn.COUNT(Member.memid).over(), Member.firstname,
                     Member.surname)
             .order_by(Member.joindate))

  Produce a numbered list of members

Produce a monotonically increasing numbered list of members, ordered by their date of joining. Remember that member IDs are not guaranteed to be sequential.

Postgres ONLY (as written).

    SELECT row_number() OVER (ORDER BY joindate), firstname, surname
    FROM members ORDER BY joindate;

    query = (Member
             .select(fn.row_number().over(order_by=[Member.joindate]),
                     Member.firstname, Member.surname)
             .order_by(Member.joindate))

  Output the facility id that has the highest number of slots booked, again

Output the facility id that has the highest number of slots booked. Ensure that in the event of a tie, all tieing results get output.

Postgres ONLY (as written).

    SELECT facid, total FROM (
      SELECT facid, SUM(slots) AS total,
             rank() OVER (order by SUM(slots) DESC) AS rank
      FROM bookings
      GROUP BY facid
    ) AS ranked WHERE rank = 1

    rank = fn.rank().over(order_by=[fn.SUM(Booking.slots).desc()])
    subq = (Booking
            .select(Booking.facility, fn.SUM(Booking.slots).alias('total'),
                    rank.alias('rank'))
            .group_by(Booking.facility))
    # Here we use a plain Select() to create our query.
    query = (Select(columns=[subq.c.facid, subq.c.total])
             .from_(subq)
             .where(subq.c.rank == 1)
             .bind(db))  # We must bind() it to the database.
    # To iterate over the query results:
    for facid, total in query.tuples():
        print(facid, total)

  Rank members by (rounded) hours used

Produce a list of members, along with the number of hours they've booked in facilities, rounded to the nearest ten hours. Rank them by this rounded figure, producing output of first name, surname, rounded hours, rank. Sort by rank, surname, and first name.

Postgres ONLY (as written).

    SELECT firstname, surname,
    ((SUM(bks.slots)+10)/20)*10 as hours,
    rank() over (order by ((sum(bks.slots)+10)/20)*10 desc) as rank
    FROM members AS mems
    INNER JOIN bookings AS bks ON mems.memid = bks.memid
    GROUP BY mems.memid
    ORDER BY rank, surname, firstname;

    hours = ((fn.SUM(Booking.slots) + 10) / 20) * 10
    query = (Member
             .select(Member.firstname, Member.surname, hours.alias('hours'),
                     fn.rank().over(order_by=[hours.desc()]).alias('rank'))
             .join(Booking)
             .group_by(Member.memid)
             .order_by(SQL('rank'), Member.surname, Member.firstname))

  Find the top three revenue generating facilities

Produce a list of the top three revenue generating facilities (including ties). Output facility name and rank, sorted by rank and facility name.

Postgres ONLY (as written).

    SELECT name, rank FROM (
        SELECT f.name, RANK() OVER (ORDER BY SUM(
            CASE WHEN memid = 0 THEN slots * f.guestcost
            ELSE slots * f.membercost END) DESC) AS rank
        FROM bookings
        INNER JOIN facilities AS f ON bookings.facid = f.facid
        GROUP BY f.name) AS subq
    WHERE rank <= 3
    ORDER BY rank;

    total_cost = fn.SUM(Case(None, (
        (Booking.member == 0, Booking.slots * Facility.guestcost),
    ), (Booking.slots * Facility.membercost)))
    subq = (Facility
            .select(Facility.name,
                    fn.RANK().over(order_by=[total_cost.desc()]).alias('rank'))
            .join(Booking)
            .group_by(Facility.name))
    query = (Select(columns=[subq.c.name, subq.c.rank])
             .from_(subq)
             .where(subq.c.rank <= 3)
             .order_by(subq.c.rank)
             .bind(db))  # Here again we used plain Select, and call bind().

  Classify facilities by value

Classify facilities into equally sized groups of high, average, and low based on their revenue. Order by classification and facility name.

Postgres ONLY (as written).

    SELECT name,
      CASE class WHEN 1 THEN 'high' WHEN 2 THEN 'average' ELSE 'low' END
    FROM (
      SELECT f.name, ntile(3) OVER (ORDER BY SUM(
        CASE WHEN memid = 0 THEN slots * f.guestcost ELSE slots * f.membercost
        END) DESC) AS class
      FROM bookings INNER JOIN facilities AS f ON bookings.facid = f.facid
      GROUP BY f.name
    ) AS subq
    ORDER BY class, name;

    cost = fn.SUM(Case(None, (
        (Booking.member == 0, Booking.slots * Facility.guestcost),
    ), (Booking.slots * Facility.membercost)))
    subq = (Facility
            .select(Facility.name,
                    fn.NTILE(3).over(order_by=[cost.desc()]).alias('klass'))
            .join(Booking)
            .group_by(Facility.name))
    klass_case = Case(subq.c.klass, [(1, 'high'), (2, 'average')], 'low')
    query = (Select(columns=[subq.c.name, klass_case])
             .from_(subq)
             .order_by(subq.c.klass, subq.c.name)
             .bind(db))

  Recursion

Common Table Expressions allow us to, effectively, create our own temporary tables for the duration of a query - they're largely a convenience to help us make more readable SQL. Using the WITH RECURSIVE modifier, however, it's possible for us to create recursive queries. This is enormously advantageous for working with tree and graph-structured data - imagine retrieving all of the relations of a graph node to a given depth, for example.

  Find the upward recommendation chain for member ID 27

Find the upward recommendation chain for member ID 27: that is, the member who recommended them, and the member who recommended that member, and so on. Return member ID, first name, and surname. Order by descending member id.

    WITH RECURSIVE recommenders(recommender) as (
      SELECT recommendedby FROM members WHERE memid = 27
      UNION ALL
      SELECT mems.recommendedby
      FROM recommenders recs
      INNER JOIN members AS mems ON mems.memid = recs.recommender
    )
    SELECT recs.recommender, mems.firstname, mems.surname
    FROM recommenders AS recs
    INNER JOIN members AS mems ON recs.recommender = mems.memid
    ORDER By memid DESC;

    # Base-case of recursive CTE. Get member recommender where memid=27.
    base = (Member
            .select(Member.recommendedby)
            .where(Member.memid == 27)
            .cte('recommenders', recursive=True, columns=('recommender',)))
    # Recursive term of CTE. Get recommender of previous recommender.
    MA = Member.alias()
    recursive = (MA
                 .select(MA.recommendedby)
                 .join(base, on=(MA.memid == base.c.recommender)))
    # Combine the base-case with the recursive term.
    cte = base.union_all(recursive)
    # Select from the recursive CTE, joining on member to get name info.
    query = (cte
             .select_from(cte.c.recommender, Member.firstname, Member.surname)
             .join(Member, on=(cte.c.recommender == Member.memid))
             .order_by(Member.memid.desc()))

  API Reference

This document specifies Peewee's APIs.

  On this page

•   Database

•   Model

•   Fields

•   Schema Manager

•   Query-builder

•   Queries

•   Query-builder Internals

•   Constants and Helpers

•   Playhouse Reference

  Database

class Database(database, thread_safe=True, field_types=None, operations=None, autoconnect=True, **kwargs)

Parameters

•   database (str) -- Database name or filename for SQLite (or None to defer initialization <#initializing-database>, in which case you must call Database.init(), specifying the database name).

•   thread_safe (bool) -- Whether to store connection state in a thread-local.

•   field_types (dict) -- A mapping of additional field types to support.

•   operations (dict) -- A mapping of additional operations to support.

•   autoconnect (bool) -- Automatically connect to database if attempting to execute a query on a closed database.

•   kwargs -- Arbitrary keyword arguments that will be passed to the database driver when a connection is created, for example password, host, etc.

    The Database is responsible for:

•   Executing queries

•   Managing connections

•   Transactions

•   Introspection

    Note: The database can be instantiated with None as the database name if the database is not known until run-time. In this way you can create a database instance and then configure it elsewhere when the settings are known. This is called deferred initialization <#initializing-database>.

    Examples:

        # Sqlite database using WAL-mode and 64MB page-cache.
        db = SqliteDatabase('app.db', pragmas={
            'journal_mode': 'wal',
            'cache_size': -64000})
        # Postgresql database on remote host.
        db = PostgresqlDatabase(
            'my_app',
            user='postgres',
            host='10.8.0.3',
            password='secret')

    Deferred initialization example:

        db = PostgresqlDatabase(None)
        class BaseModel(Model):
            class Meta:
                database = db
        # Read database connection info from env, for example:
        db_name = os.environ['DATABASE']
        db_host = os.environ['PGHOST']
        # Initialize database.
        db.init(db_name, host=db_host, user='postgres')

param = '?'
    String used as parameter placeholder in SQL queries.

quote = '"'
    Type of quotation-mark to use to denote entities such as tables or columns.

init(database, **kwargs)

Parameters

•   database (str) -- Database name or filename for SQLite.

•   kwargs -- Arbitrary keyword arguments that will be passed to the database driver when a connection is created, for example password, host, etc.

    Initialize a deferred database. See Initializing the Database <#initializing-database> for more info.

connect(reuse_if_open=False)

Parameters
    reuse_if_open (bool) -- Do not raise an exception if a connection is already opened.

Returns
    whether a new connection was opened.

Return type
    bool

Raises
    OperationalError if connection already open and reuse_if_open is not set to True.

    Open a connection to the database.

        db.connect()
        # Or:
        db.connect(reuse_if_open=True)

close()

Returns
    Whether the connection was closed. If the database was already closed, this returns False.

Return type
    bool

    Close the connection to the database.

        if not db.is_closed():
            db.close()

is_closed()

Returns
    return True if database is closed, False if open.

Return type
    bool

connection()
    Return the DB-API driver connection. If a connection is not open, one will be opened.

        db = SqliteDatabase(':memory:')
        # Get the sqlite3.Connection() instance.
        conn = db.connection()

__enter__()

__exit__(exc_type, exc_val, exc_tb)

__call__()
    The database object can be used as a context manager or decorator.

1.  Connection opens when context manager / decorated function is entered.

2.  Peewee begins a transaction.

3.  Control is passed to user for duration of block.

4.  Peewee commits transaction if block exits cleanly, otherwise issues a rollback.

5.  Peewee closes the connection.

6.  Any unhandled exception is raised.

        with db:
            User.create(username='charlie')
            # Transaction is committed when the block exits normally,
            # rolled back if an exception is raised.

    Decorator:

        @db
        def demo():
            print('closed?', db.is_closed())
        demo()  # "closed? False"
        db.is_closed()  # True

connection_context()
    Create a context-manager or decorator that will hold open a connection for the duration of the wrapped block.

    Example:

        with db.connection_context():
            # Connection is open; no implicit transaction.
            results = User.select()

    Decorator:

        @db.connection_context()
        def load_fixtures():
            db.create_tables([User, Tweet])
            import_data()
                       database.create_tables(APP_MODELS)

cursor(named_cursor=None)

Parameters
    named_cursor -- Reserved for internal use.

    Return a DB-API cursor object on the current connection. If a connection is not open, one will be opened.

execute_sql(sql, params=None)

Parameters

•   sql (str) -- SQL string to execute.

•   params (tuple) -- Parameters for query.

Returns
    cursor object.

    Execute a SQL query and return a cursor over the results.

        db = SqliteDatabase('my_app.db')
        db.connect()
        # Example of executing a simple query and ignoring the results.
        db.execute_sql("ATTACH DATABASE ':memory:' AS cache;")
        # Example of iterating over the results of a query using the cursor.
        cursor = db.execute_sql('SELECT * FROM users WHERE status = ?', (ACTIVE,))
        for row in cursor.fetchall():
            # Do something with row, which is a tuple containing column data.
            pass

execute(query, **context_options)

Parameters

•   query -- A Query instance.

•   context_options -- Arbitrary options to pass to the SQL generator.

Returns
    cursor object.

    Execute a SQL query by compiling a Query instance and executing the resulting SQL.

        query = User.insert({'username': 'Huey'})
        db.execute(query)  # Equivalent to query.execute()

last_insert_id(cursor, query_type=None)

Parameters
    cursor -- cursor object.

Returns
    primary key of last-inserted row.

rows_affected(cursor)

Parameters
    cursor -- cursor object.

Returns
    number of rows modified by query.

atomic(...)
    Create a context-manager or decorator which wraps a block of code in a transaction (or savepoint).

    Calls to atomic() can be nested.

    Database-specific parameters:

    PostgresqlDatabase and MySQLDatabase accept an isolation_level parameter. SqliteDatabase accepts a lock_type parameter. Refer to SQLite Transaction Locking Modes <#sqlite-locking> and Postgresql Isolation Notes <#postgres-isolation> for discussion.

Parameters

•   isolation_level (str) -- Isolation strategy: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE

•   lock_type (str) -- Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.

    Example code:

        with db.atomic() as txn:
            user = User.create(username='charlie')
            with db.atomic():
                tweet = Tweet.create(user=user, content='Hello')
        # Both rows are committed when block exits normally.

    As a decorator:

        @db.atomic()
        def create_user_with_tweet(username, content):
            user = User.create(username=username)
            Tweet.create(user=user, content=content)
            return user

    Transactions (and save-points) can be committed or rolled-back within the wrapped block. If this occurs, a new transaction or savepoint is begun:

    Example:

        with db.atomic() as txn:
            User.create(username='mickey')
            txn.commit()  # Changes are saved and a new transaction begins.
            User.create(username='huey')
            txn.rollback()  # "huey" will not be saved.
            User.create(username='zaizee')
        # Print the usernames of all users.
        print([u.username for u in User.select()])
        # Prints ["mickey", "zaizee"]

    If an unhandled exception occurs in the block, the block is rolled-back and the exception propagates.

transaction(...)
    Create a context-manager or decorator that runs all queries in the wrapped block in a transaction.

    Database-specific parameters:

    PostgresqlDatabase and MySQLDatabase accept an isolation_level parameter. SqliteDatabase accepts a lock_type parameter. Refer to SQLite Transaction Locking Modes <#sqlite-locking> and Postgresql Isolation Notes <#postgres-isolation> for discussion.

Parameters

•   isolation_level (str) -- Isolation strategy: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE

•   lock_type (str) -- Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.

        with db.transaction() as txn:
            User.create(username='mickey')
            txn.commit()         # Commit now; a new transaction begins.
            User.create(username='huey')
            txn.rollback()       # Roll back huey; a new transaction begins.
            User.create(username='zaizee')
        # zaizee is committed when the block exits.

    Note: Transactions can be committed or rolled-back within the wrapped block. If this occurs, a new transaction is begun.

    Warning: If you attempt to nest transactions with peewee using the transaction() context manager, only the outer-most transaction will be used.

    As this may lead to unpredictable behavior, it is recommended that you use atomic().

savepoint()
    Create a context-manager or decorator that runs all queries in the wrapped block in a savepoint. Savepoints can be nested arbitrarily, but must occur within a transaction.

        with db.transaction() as txn:
            with db.savepoint() as sp:
                User.create(username='mickey')
            with db.savepoint() as sp2:
                User.create(username='zaizee')
                sp2.rollback()  # "zaizee" is not saved.
                User.create(username='huey')
        # mickey and huey were created.

    Note: Savepoints can be committed or rolled-back within the wrapped block. If this occurs, a new savepoint is begun.

manual_commit()
    Create a context-manager or decorator which disables Peewee's transaction management for the wrapped block.

    Example:

        with db.manual_commit():
            db.begin()  # Begin transaction explicitly.
            try:
                user.delete_instance(recursive=True)
            except:
                db.rollback()  # Rollback -- an error occurred.
                raise
            else:
                try:
                    db.commit()  # Attempt to commit changes.
                except:
                    db.rollback()  # Error committing, rollback.
                    raise

    The above code is equivalent to the following:

        with db.atomic():
            user.delete_instance(recursive=True)

session_start()
    Begin a new transaction (without using a context-manager or decorator). This method is useful if you intend to execute a sequence of operations inside a transaction, but using a decorator or context-manager would not be appropriate.

    Note: It is strongly advised that you use the Database.atomic() method whenever possible for managing transactions/savepoints. The atomic method correctly manages nesting, uses the appropriate construction (e.g., transaction-vs-savepoint), and always cleans up after itself.

    The session_start() method should only be used if the sequence of operations does not easily lend itself to wrapping using either a context-manager or decorator.

    Warning: You must always call either session_commit() or session_rollback() after calling the session_start method.

session_commit()
    Commit any changes made during a transaction begun with session_start().

session_rollback()
    Roll back any changes made during a transaction begun with session_start().

begin()
    Begin a transaction when using manual-commit mode.

    Note: This method should only be used in conjunction with the manual_commit() context manager.

commit()
    Manually commit the currently-active transaction.

    Note: This method should only be used in conjunction with the manual_commit() context manager.

rollback()
    Manually roll-back the currently-active transaction.

    Note: This method should only be used in conjunction with the manual_commit() context manager.

in_transaction()

Returns
    whether or not a transaction is currently open.

Return type
    bool

        with db.atomic() as tx:
            assert db.in_transaction()
        assert not db.in_transaction()  # No longer in transaction.

batch_commit(it, n)

Parameters

•   it (iterable) -- an iterable whose items will be yielded.

•   n (int) -- commit every n items.

Returns
    an equivalent iterable to the one provided, with the addition that groups of n items will be yielded in a transaction.

    Simplify batching large operations, such as inserts, updates, etc. Pass in an iterable and the number of items-per-batch, and the items will be returned by an equivalent iterator that wraps each batch in a transaction.

    Example:

        # Some list or iterable containing data to insert.
        row_data = [{'username': 'u1'}, {'username': 'u2'}, ...]
        # Insert all data, committing every 100 rows. If, for example,
        # there are 789 items in the list, then there will be a total of
        # 8 transactions (7x100 and 1x89).
        for row in db.batch_commit(row_data, 100):
            user = User.create(**row)
            # Now let's suppose we need to do something w/the user.
            user.call_method()

    A more efficient option is to batch the data into a multi-value INSERT statement (for example, using Model.insert_many()). Use this approach instead wherever possible:

        with db.atomic():
            for idx in range(0, len(row_data), 100):
                # Insert 100 rows at a time.
                rows = row_data[idx:idx + 100]
                User.insert_many(rows).execute()

table_exists(table, schema=None)

Parameters

•   table (str) -- Table name.

•   schema (str) -- Schema name (optional).

Returns
    bool indicating whether table exists.

get_tables(schema=None)

Parameters
    schema (str) -- Schema name (optional).

Returns
    a list of table names in the database.

get_indexes(table, schema=None)

Parameters

•   table (str) -- Table name.

•   schema (str) -- Schema name (optional).

    Return a list of IndexMetadata tuples.

    Example:

        print(db.get_indexes('entry'))
        [IndexMetadata(
             name='entry_public_list',
             sql='CREATE INDEX "entry_public_list" ...',
             columns=['timestamp'],
             unique=False,
             table='entry'),
         IndexMetadata(
             name='entry_slug',
             sql='CREATE UNIQUE INDEX "entry_slug" ON "entry" ("slug")',
             columns=['slug'],
             unique=True,
             table='entry')]

get_columns(table, schema=None)

Parameters

•   table (str) -- Table name.

•   schema (str) -- Schema name (optional).

    Return a list of ColumnMetadata tuples.

    Example:

        print(db.get_columns('entry'))
        [ColumnMetadata(
             name='id',
             data_type='INTEGER',
             null=False,
             primary_key=True,
             table='entry'),
         ColumnMetadata(
             name='title',
             data_type='TEXT',
             null=False,
             primary_key=False,
             table='entry'),
         ...]

get_primary_keys(table, schema=None)

Parameters

•   table (str) -- Table name.

•   schema (str) -- Schema name (optional).

    Return a list of column names that comprise the primary key.

    Example:

        print(db.get_primary_keys('entry'))
        ['id']

get_foreign_keys(table, schema=None)

Parameters

•   table (str) -- Table name.

•   schema (str) -- Schema name (optional).

    Return a list of ForeignKeyMetadata tuples for keys present on the table.

    Example:

        print(db.get_foreign_keys('entrytag'))
        [ForeignKeyMetadata(
             column='entry_id',
             dest_table='entry',
             dest_column='id',
             table='entrytag'),
         ...]

get_views(schema=None)

Parameters
    schema (str) -- Schema name (optional).

    Return a list of ViewMetadata tuples for VIEWs present in the database.

    Example:

        print(db.get_views())
        [ViewMetadata(
             name='entries_public',
             sql='CREATE VIEW entries_public AS SELECT ... '),
         ...]

sequence_exists(seq)

Parameters
    seq (str) -- Name of sequence.

Returns
    Whether sequence exists.

Return type
    bool

        if db.sequence_exists('user_id_seq'):
            print('Sequence found.')

create_tables(models, **options)

Parameters

•   models (list) -- A list of Model classes.

•   options -- Options to specify when calling Model.create_table().

    Create tables, indexes and associated constraints for the given list of models.

    Dependencies are resolved so that tables are created in the appropriate order.

drop_tables(models, **options)

Parameters

•   models (list) -- A list of Model classes.

•   kwargs -- Options to specify when calling Model.drop_table().

    Drop tables, indexes and constraints for the given list of models.

    Dependencies are resolved so that tables are dropped in the appropriate order.

bind(models, bind_refs=True, bind_backrefs=True)

Parameters

•   models (list) -- One or more Model classes to bind.

•   bind_refs (bool) -- Bind related models.

•   bind_backrefs (bool) -- Bind back-reference related models.

    Bind the given list of models, and specified relations, to the database.

        def setup_tests():
            # Bind models to an in-memory SQLite database.
            test_db = SqliteDatabase(':memory:')
            test_db.bind([User, Tweet])

bind_ctx(models, bind_refs=True, bind_backrefs=True)

Parameters

•   models (list) -- List of models to bind to the database.

•   bind_refs (bool) -- Bind models that are referenced using foreign-keys.

•   bind_backrefs (bool) -- Bind models that reference the given model with a foreign-key.

    Create a context-manager that binds (associates) the given models with the current database for the duration of the wrapped block.

    Example:

        MODELS = [User, Tweet, Favorite]
        # Bind the given models to the db for the duration of wrapped block.
        def use_test_database(fn):
            @wraps(fn)
            def inner(self):
                with test_db.bind_ctx(MODELS):
                    test_db.create_tables(MODELS)
                    try:
                        fn(self)
                    finally:
                        test_db.drop_tables(MODELS)
            return inner
        class TestSomething(TestCase):
            @use_test_database
            def test_something(self):
                # ... models are bound to test database ...
                pass

extract_date(date_part, date_field)

Parameters

•   date_part (str) -- date part to extract, e.g. 'year'.

•   date_field (Node <#Node>) -- a SQL node containing a date/time, for example a DateTimeField.

Returns
    a SQL node representing a function call that will return the provided date part.

    Provides a compatible interface for extracting a portion of a datetime.

truncate_date(date_part, date_field)

Parameters

•   date_part (str) -- date part to truncate to, e.g. 'day'.

•   date_field (Node <#Node>) -- a SQL node containing a date/time, for example a DateTimeField.

Returns
    a SQL node representing a function call that will return the truncated date part.

    Provides a compatible interface for truncating a datetime to the given resolution.

random()

Returns
    a SQL node representing a function call that returns a random value.

    A compatible interface for calling the appropriate random number generation function provided by the database. For Postgres and Sqlite, this is equivalent to fn.random(), for MySQL fn.rand().

class SqliteDatabase(database, pragmas=None, regexp_function=False, rank_functions=False, timeout=5, returning_clause=None, **kwargs)

Parameters

•   pragmas -- Either a dictionary or a list of 2-tuples containing pragma key and value to set every time a connection is opened.

•   regexp_function (bool) -- Make the REGEXP function available.

•   rank_functions (bool) -- Make the full-text search ranking functions available (recommended only if using FTS4).

•   timeout -- Set the busy-timeout on the SQLite driver (in seconds).

•   returning_clause (bool) -- Use RETURNING clause automatically for bulk INSERT queries (requires Sqlite 3.35 or newer).

    Sqlite database implementation. SqliteDatabase that provides some advanced features only offered by Sqlite.

•   Register user-defined functions, aggregates, window functions, collations

•   Load extension modules distributed as shared libraries

•   Advanced transactions (specify lock type)

•   For additional features see CySqliteDatabase.

    Example of initializing a database and configuring some PRAGMAs:

        db = SqliteDatabase('my_app.db', pragmas=(
            ('cache_size', -16000),  # 16MB
            ('journal_mode', 'wal'),  # Use write-ahead-log journal mode.
        ))
        # Alternatively, pragmas can be specified using a dictionary.
        db = SqliteDatabase('my_app.db', pragmas={'journal_mode': 'wal'})

pragma(key, value=SENTINEL, permanent=False)

Parameters

•   key -- Setting name.

•   value -- New value for the setting (optional).

•   permanent -- Apply this pragma whenever a connection is opened.

    Execute a PRAGMA query once on the active connection. If a value is not specified, then the current value will be returned.

    If permanent is specified, then the PRAGMA query will also be executed whenever a new connection is opened, ensuring it is always in-effect.

cache_size
    Get or set the cache_size pragma for the current connection.

foreign_keys
    Get or set the foreign_keys pragma for the current connection.

journal_mode
    Get or set the journal_mode pragma.

journal_size_limit
    Get or set the journal_size_limit pragma.

mmap_size
    Get or set the mmap_size pragma for the current connection.

page_size
    Get or set the page_size pragma.

read_uncommitted
    Get or set the read_uncommitted pragma for the current connection.

synchronous
    Get or set the synchronous pragma for the current connection.

wal_autocheckpoint
    Get or set the wal_autocheckpoint pragma for the current connection.

timeout
    Get or set the busy timeout (seconds).

func(name=None, num_params=-1, deterministic=None)

Parameters

•   name (str) -- Name of the function (defaults to function name).

•   num_params (int) -- Number of parameters the function accepts, or -1 for any number.

•   deterministic (bool) -- Whether the function is deterministic for a given input (this is required to use the function in an index). Requires Sqlite 3.20 or newer, and sqlite3 driver support (added to stdlib in Python 3.8).

    Decorator to register a user-defined scalar function.

    Example:

        @db.func('title_case')
        def title_case(s):
            return s.title() if s else ''
        Book.select(fn.title_case(Book.title))

register_function(fn, name=None, num_params=-1, deterministic=None)

Parameters

•   fn -- The user-defined scalar function.

•   name (str) -- Name of function (defaults to function name)

•   num_params (int) -- Number of arguments the function accepts, or -1 for any number.

•   deterministic (bool) -- Whether the function is deterministic for a given input (this is required to use the function in an index). Requires Sqlite 3.20 or newer, and sqlite3 driver support (added to stdlib in Python 3.8).

    Register a user-defined scalar function. The function will be registered each time a new connection is opened. Additionally, if a connection is already open, the function will be registered with the open connection.

aggregate(name=None, num_params=-1)

Parameters

•   name (str) -- Name of the aggregate (defaults to class name).

•   num_params (int) -- Number of parameters the aggregate accepts, or -1 for any number.

    Class decorator to register a user-defined aggregate function.

    Example:

        @db.aggregate('md5')
        class MD5(object):
            def initialize(self):
                self.md5 = hashlib.md5()
            def step(self, value):
                self.md5.update(value)
            def finalize(self):
                return self.md5.hexdigest()
        @db.aggregate()
        class Product(object):
            '''Like SUM() except calculates cumulative product.'''
            def __init__(self):
                self.product = 1
            def step(self, value):
                self.product *= value
            def finalize(self):
                return self.product

register_aggregate(klass, name=None, num_params=-1)

Parameters

•   klass -- Class implementing aggregate API.

•   name (str) -- Aggregate function name (defaults to name of class).

•   num_params (int) -- Number of parameters the aggregate accepts, or -1 for any number.

    Register a user-defined aggregate function.

    The function will be registered each time a new connection is opened. Additionally, if a connection is already open, the aggregate will be registered with the open connection.

window_function(name=None, num_params=-1)

Parameters

•   name (str) -- Name of the window function (defaults to class name).

•   num_params (int) -- Number of parameters the function accepts, or -1 for any number.

    Class decorator to register a user-defined window function. Window functions must define the following methods:

•   step(<params>) - receive values from a row and update state.

•   inverse(<params>) - inverse of step() for the given values.

•   value() - return the current value of the window function.

•   finalize() - return the final value of the window function.

    Example:

        @db.window_function('my_sum')
        class MySum(object):
            def __init__(self):
                self._value = 0
            def step(self, value):
                self._value += value
            def inverse(self, value):
                self._value -= value
            def value(self):
                return self._value
            def finalize(self):
                return self._value

register_window_function(klass, name=None, num_params=-1)

Parameters

•   klass -- Class implementing window function API.

•   name (str) -- Window function name (defaults to name of class).

•   num_params (int) -- Number of parameters the function accepts, or -1 for any number.

    Register a user-defined window function, requires SQLite >= 3.25.0.

    The window function will be registered each time a new connection is opened. Additionally, if a connection is already open, the window function will be registered with the open connection.

collation(name=None)

Parameters
    name (str) -- Name of collation (defaults to function name)

    Decorator to register a user-defined collation.

    Example:

        @db.collation('reverse')
        def collate_reverse(s1, s2):
            return -cmp(s1, s2)
        # Usage:
        Book.select().order_by(collate_reverse.collation(Book.title))
        # Equivalent:
        Book.select().order_by(Book.title.asc(collation='reverse'))

    As you might have noticed, the original collate_reverse function has a special attribute called collation attached to it. This extra attribute provides a shorthand way to generate the SQL necessary to use our custom collation.

register_collation(fn, name=None)

Parameters

•   fn -- The collation function.

•   name (str) -- Name of collation (defaults to function name)

    Register a user-defined collation. The collation will be registered each time a new connection is opened. Additionally, if a connection is already open, the collation will be registered with the open connection.

unregister_function(name)

Parameters
    name -- Name of the user-defined scalar function.

    Unregister the user-defined scalar function.

unregister_aggregate(name)

Parameters
    name -- Name of the user-defined aggregate.

    Unregister the user-defined aggregate.

unregister_window_function(name)

Parameters
    name -- Name of the user-defined window function.

    Unregister the user-defined window function.

unregister_collation(name)

Parameters
    name -- Name of the user-defined collation.

    Unregister the user-defined collation.

load_extension(extension_module)
    Load the given extension shared library. Extension will be loaded for the current connection as well as all subsequent connections.

        db = SqliteDatabase('my_app.db')
        # Load extension in closure.so shared library.
        db.load_extension('closure')

unload_extension(extension_module)
    Unregister extension from being automatically loaded on new connections.

attach(filename, name)

Parameters

•   filename (str) -- Database to attach (or :memory: for in-memory)

•   name (str) -- Schema name for attached database.

Returns
    boolean indicating success

    Register another database file that will be attached to every database connection. If the main database is currently connected, the new database will be attached on the open connection.

    Note: Databases that are attached using this method will be attached every time a database connection is opened.

detach(name)

Parameters
    name (str) -- Schema name for attached database.

Returns
    boolean indicating success

    Unregister another database file that was attached previously with a call to attach(). If the main database is currently connected, the attached database will be detached from the open connection.

atomic(lock_type=None)

Parameters
    lock_type (str) -- Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.

    Create an atomic context-manager / decorator, optionally using the specified locking strategy (default DEFERRED).

    Lock type only applies to the outermost atomic() block.

    See also: SQLite Transaction Locking Modes <#sqlite-locking>

transaction(lock_type=None)

Parameters
    lock_type (str) -- Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.

    Create a transaction context-manager / decorator, optionally using the specified locking strategy (default DEFERRED).

class PostgresqlDatabase(database, register_unicode=True, encoding=None, isolation_level=None, prefer_psycopg3=False)
    Postgresql database implementation. Uses psycopg2 or psycopg3.

    Additional optional keyword-parameters:

Parameters

•   register_unicode (bool) -- Register unicode types.

•   encoding (str) -- Database encoding.

•   isolation_level (int, str) -- Isolation level constant, defined in the psycopg2.extensions module or psycopg.IsolationLevel enum (psycopg3). Also accepts string which is converted to the matching constant.

•   prefer_psycopg3 (bool) -- If both psycopg2 and psycopg3 are installed, instruct Peewee to prefer psycopg3.

    Example:

        db = PostgresqlDatabase(
            'app',
            user='postgres',
            host='10.8.0.1',
            port=5432,
            password=os.environ['PGPASSWORD'],
            isolation_level='SERIALIZABLE')

set_time_zone(timezone)

Parameters
    timezone (str) -- timezone name, e.g. "US/Central".

Returns
    no return value.

    Set the timezone on the current connection. If no connection is open, then one will be opened.

set_isolation_level(isolation_level)

Parameters
    isolation_level (int, str) -- Isolation level constant, defined in the psycopg2.extensions module or psycopg.IsolationLevel enum (psycopg3). Also accepts string which is converted to the matching constant. Set to None to use the server default.

    Example of setting isolation level:

        # psycopg2 or psycopg3
        db = db.set_isolation_level('SERIALIZABLE')
        # psycopg2
        from psycopg2.extensions import ISOLATION_LEVEL_SERIALIZABLE
        db.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
        # psycopg3
        from psycopg import IsolationLevel
        db.set_isolation_level(IsolationLevel.SERIALIZABLE)

    Isolation level values in order of increasing strictness:

•   READ UNCOMMITTED

•   READ COMMITTED

•   REPEATABLE READ

•   SERIALIZABLE

    See the Postgresql transaction isolation docs <https://www.postgresql.org/docs/current/transaction-iso.html> and Peewee's Postgresql Isolation Notes <#postgres-isolation> for additional discussion.

atomic(isolation_level=None)

Parameters
    isolation_level (int, str) -- Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED

    Create an atomic context-manager, optionally using the specified isolation level (if unspecified, the connection default will be used).

    Isolation level only applies to the outermost atomic() block.

    See the Postgresql transaction isolation docs <https://www.postgresql.org/docs/current/transaction-iso.html> and Peewee's Postgresql Isolation Notes <#postgres-isolation> for additional discussion.

transaction(isolation_level=None)

Parameters
    isolation_level (int, str) -- Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED

    Create a transaction context-manager, optionally using the specified isolation level (if unspecified, the connection default will be used).

class MySQLDatabase(database, **kwargs)
    MySQL database implementation.

    Example:

        db = MySQLDatabase('app', host='10.8.0.1')

atomic(isolation_level=None)

Parameters
    isolation_level (str) -- Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED

    Create an atomic context-manager, optionally using the specified isolation level (if unspecified, the server default will be used).

    Isolation level only applies to the outermost atomic() block.

transaction(isolation_level=None)

Parameters
    isolation_level (str) -- Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED

    Create a transaction context-manager, optionally using the specified isolation level (if unspecified, the server default will be used).

  Model

class Model(**kwargs)

Parameters
    kwargs -- Mapping of field-name to value to initialize model with.

    Model class provides a high-level abstraction for working with database tables. Models are a one-to-one mapping with a database table (or a table-like object, such as a view). Subclasses of Model declare any number of Field instances as class attributes. These fields correspond to columns on the table.

    Table-level operations, such as select(), update(), insert() and delete() are implemented as classmethods.

    Row-level operations, such as save() and delete_instance() are implemented as instancemethods.

    Example:

        db = SqliteDatabase(':memory:')
        class User(Model):
            username = TextField()
            join_date = DateTimeField(default=datetime.datetime.now)
            is_admin = BooleanField(default=False)
        admin = User(username='admin', is_admin=True)
        admin.save()

classmethod alias([alias=None])

Parameters
    alias (str) -- Optional name for alias.

Returns
    ModelAlias instance.

    Create an alias to the model-class. Model aliases allow you to reference the same Model multiple times in a query, for example when doing a self-join or sub-query.

    Example:

        Parent = Category.alias()
        sq = (Category
              .select(Category, Parent)
              .join(Parent, on=(Category.parent == Parent.id))
              .where(Parent.name == 'parent category'))

classmethod select(*fields)

Parameters
    fields -- A list of model classes, field instances, functions or expressions. If no arguments are provided, all columns for the given model will be selected by default.

Returns
    ModelSelect query.

    Create a SELECT query. If no fields are explicitly provided, the query will by default SELECT all the fields defined on the model, unless you are using the query as a sub-query, in which case only the primary key will be selected by default.

    Example of selecting all columns:

        query = User.select().where(User.active == True).order_by(User.username)

    Example of selecting all columns on Tweet and the parent model, User. When the user foreign key is accessed on a Tweet instance no additional query will be needed (see N+1 <#nplusone> for more details):

        query = (Tweet
                 .select(Tweet, User)
                 .join(User)
                 .order_by(Tweet.created_date.desc()))
        for tweet in query:
            print(tweet.user.username, '->', tweet.content)

    Example of subquery only selecting the primary key:

        inactive_users = User.select().where(User.active == False)
        # Here, instead of defaulting to all columns, Peewee will default
        # to only selecting the primary key.
        Tweet.delete().where(Tweet.user.in_(inactive_users)).execute()

    See Querying <#querying> for in-depth discussion.

classmethod update(__data=None, **update)

Parameters

•   __data (dict) -- dict of fields to values.

•   update -- Field-name to value mapping.

    Create an UPDATE query.

    Example showing users being marked inactive if their registration has expired:

        q = (User
             .update(active=False)
             .where(User.registration_expired == True))
        q.execute()  # Execute the query, returning number of rows updated.

    Example showing an atomic update:

        q = (PageView
             .update({PageView.count: PageView.count + 1})
             .where(PageView.url == url))
        q.execute()  # Execute the query.

    Update queries support returning() with Postgresql and SQLite to obtain the updated rows:

        query = (User
                 .update(spam=True)
                 .where(User.username.contains('billing'))
                 .returning(User))
        for user in query:
            print(f'Marked {user.username} as spam')

    See Updating Records <#updating-records> for additional discussion.

    Note: When an update query is executed, the number of rows modified will be returned.

classmethod insert(__data=None, **insert)

Parameters

•   __data (dict) -- dict of fields to values to insert.

•   insert -- Field-name to value mapping.

    Create an INSERT query.

    Insert a new row into the database. If any fields on the model have default values, these values will be used if the fields are not explicitly set in the insert dictionary.

    Example showing creation of a new user:

        q = User.insert(username='admin', active=True, registration_expired=False)
        q.execute()  # perform the insert.

    You can also use Field objects as the keys:

        new_id = User.insert({User.username: 'admin'}).execute()

    If you have a model with a default value on one of the fields, and that field is not specified in the insert parameter, the default will be used:

        class User(Model):
            username = CharField()
            active = BooleanField(default=True)
        # This INSERT query will automatically specify `active=True`:
        User.insert(username='charlie')

    Insert queries support returning() with Postgresql and SQLite to obtain the inserted rows:

        alice, = (User
                  .insert(username='alice')
                  .returning(User)
                  .execute())
        print(f'Added {alice.username} with id = {alice.id}')

    Note: When an insert query is executed on a table with an auto-incrementing primary key, the primary key of the new row will be returned.

classmethod insert_many(rows, fields=None)

Parameters

•   rows -- An iterable that yields rows to insert.

•   fields (list) -- List of fields being inserted.

Returns
    number of rows modified (see note).

    INSERT multiple rows of data.

    The rows parameter must be an iterable that yields dictionaries or tuples, where the ordering of the tuple values corresponds to the fields specified in the fields argument. As with insert(), fields that are not specified in the dictionary will use their default value, if one exists.

    Note: Due to the nature of bulk inserts, each row must contain the same fields. The following will not work:

        Person.insert_many([
            {'first_name': 'Peewee', 'last_name': 'Herman'},
            {'first_name': 'Huey'},  # Missing "last_name"!
        ]).execute()

    Example of inserting multiple Users:

        data = [
            ('charlie', True),
            ('huey', False),
            ('zaizee', False)]
        query = User.insert_many(data, fields=[User.username, User.is_admin])
        query.execute()

    Equivalent example using dictionaries:

        data = [
            {'username': 'charlie', 'is_admin': True},
            {'username': 'huey', 'is_admin': False},
            {'username': 'zaizee', 'is_admin': False}]
        # Insert new rows.
        User.insert_many(data).execute()

    Because the rows parameter can be an arbitrary iterable, you can also use a generator:

        def get_usernames():
            for username in ['charlie', 'huey', 'peewee']:
                yield {'username': username}
        User.insert_many(get_usernames()).execute()

    Insert queries support returning() with Postgresql and SQLite to obtain the inserted rows:

        query = (User
                 .insert_many([{'username': 'alice'}, {'username': 'bob'}])
                 .returning(User))
        for user in query:
            print(f'Added {user.username} with id = {user.id}')

    See Bulk Inserts <#bulk-inserts> for additional discussion.

    Note: SQLite has a default limit of bound variables per statement. Additional discussion: Bulk Inserts <#bulk-inserts>.

    SQLite documentation:

•   Max variable number limit <https://www.sqlite.org/limits.html#max_variable_number>

•   SQLite compile-time flags <https://www.sqlite.org/compile.html>

    Note: The default return value is the number of rows modified. However, when using Postgresql, Peewee will return a cursor that yields the primary-keys of the inserted rows. To disable this functionality with Postgresql, append as_rowcount() to your insert.

classmethod insert_from(query, fields)

Parameters

•   query (Select <#Select>) -- SELECT query to use as source of data.

•   fields -- Fields to insert data into.

Returns
    number of rows modified (see note).

    Generates an INSERT INTO ... SELECT query, copying rows from one table into another without round-tripping data through Python:

        (TweetArchive
         .insert_from(
             Tweet.select(Tweet.user, Tweet.message),
             fields=[TweetArchive.user, TweetArchive.message])
         .execute())

    See Bulk Inserts <#bulk-inserts> for additional discussion.

    Note: The default return value is the number of rows modified. However, when using Postgresql, Peewee will return a cursor that yields the primary-keys of the inserted rows. To disable this functionality with Postgresql, append as_rowcount() to your insert.

classmethod replace(__data=None, **insert)

Parameters

•   __data (dict) -- dict of fields to values to insert.

•   insert -- Field-name to value mapping.

    SQLite and MySQL support a REPLACE query, which will replace the row in the event of a conflict:

        class User(BaseModel):
            username = TextField(unique=True)
            last_login = DateTimeField(null=True)
        # Insert, or replace the entire existing row.
        User.replace(username='huey', last_login=datetime.datetime.now()).execute()
        # Equivalent using insert():
        (User
         .insert(username='huey', last_login=datetime.datetime.now())
         .on_conflict_replace()
         .execute())

    See Upsert <#upsert> for additional discussion.

    Warning: replace deletes and re-inserts, which changes the primary key. Use Insert.on_conflict() when the primary key must be preserved, or when only some columns should be updated.

classmethod replace_many(rows, fields=None)

Parameters

•   rows -- An iterable that yields rows to insert.

•   fields (list) -- List of fields being inserted.

    INSERT multiple rows of data using REPLACE for conflict-resolution.

    See also:

•   Model.insert_many() for syntax and examples.

•   Upsert <#upsert> for additional discussion.

    Warning: replace_many may delete and re-insert rows, which changes the primary key. Use Insert.on_conflict() when the primary key must be preserved, or when only some columns should be updated.

classmethod raw(sql, *params)

Parameters

•   sql (str) -- SQL query to execute.

•   params -- Parameters for query.

    Execute a SQL query directly.

    Example selecting rows from the User table:

        q = User.raw('select id, username from users')
        for user in q:
            print(user.id, user.username)

    Note: Generally the use of raw is reserved for those cases where you can significantly optimize a select query.

classmethod delete()
    Create a DELETE query.

    Example showing the deletion of all inactive users:

        q = User.delete().where(User.active == False)
        q.execute()  # Remove the rows, return number of rows removed.

    Delete queries support returning() with Postgresql and SQLite to obtain the deleted rows:

        query = (User
                 .delete()
                 .where(User.username.contains('billing'))
                 .returning(User))
        for user in query:
            print(f'Deleted: {user.username}')

    See also:

•   Deleting Records <#deleting-records> for discussion and example usage.

•   Model.delete_instance() for deleting individual rows.

classmethod create(**query)

Parameters
    query -- Mapping of field-name to value.

    INSERT new row into table and return corresponding model instance.

    Example showing the creation of a user (a row will be added to the database):

        user = User.create(username='admin', password='test')

    Note: create() is a shorthand for instantiate -> save.

classmethod bulk_create(model_list, batch_size=None)

Parameters

•   model_list (iterable) -- a list or other iterable of unsaved Model instances.

•   batch_size (int) -- number of rows to batch per insert. If unspecified, all models will be inserted in a single query.

Returns
    no return value.

    Efficiently INSERT multiple unsaved model instances into the database. Unlike insert_many(), which accepts row data as a list of either dictionaries or lists, this method accepts a list of unsaved model instances.

    Example:

        user_list = [User(username='u%s' % i) for i in range(10)]
        with db.atomic():
            # All 10 users are inserted in a single query.
            User.bulk_create(user_list)

    Batches:

        user_list = [User(username='u%s' % i) for i in range(10)]
        with database.atomic():
            # Will execute 4 INSERT queries (3 batches of 3, 1 batch of 1).
            User.bulk_create(user_list, batch_size=3)

    Note:

•   The primary-key value for the newly-created models will only be set if you are using Postgresql (which supports the RETURNING clause).

•   SQLite has a limit of bound parameters for a query, typically 999 for Sqlite < 3.32.0, and 32766 for newer versions.

•   Strongly recommended that you wrap the call in a transaction using Database.atomic(). Otherwise an error in a batch mid-way through could leave the database in an inconsistent state.

classmethod bulk_update(model_list, fields, batch_size=None)

Parameters

•   model_list (iterable) -- a list or other iterable of Model instances.

•   fields (list) -- list of fields to update.

•   batch_size (int) -- number of rows to batch per insert. If unspecified, all models will be inserted in a single query.

Returns
    total number of rows updated.

    UPDATE multiple model instances in a single query by generating a CASE statement mapping ids to new field values.

    Example:

        # First, create 3 users.
        u1, u2, u3 = [User.create(username='u%s' % i) for i in (1, 2, 3)]
        # Now let's modify their usernames.
        u1.username = 'u1-x'
        u2.username = 'u2-y'
        u3.username = 'u3-z'
        # Update all three rows using a single UPDATE query.
        User.bulk_update([u1, u2, u3], fields=[User.username])

    This will result in executing the following SQL:

        UPDATE "users" SET "username" = CASE "users"."id"
            WHEN 1 THEN "u1-x"
            WHEN 2 THEN "u2-y"
            WHEN 3 THEN "u3-z" END
        WHERE "users"."id" IN (1, 2, 3);

    If you have a large number of objects to update, it is strongly recommended that you specify a batch_size and wrap the operation in a transaction:

        with database.atomic():
            User.bulk_update(user_list, fields=['username'], batch_size=50)

    Note: bulk_update may be slower than a direct UPDATE query when the list is very large, because the generated CASE expression grows proportionally. For updates that can be expressed as a single WHERE clause, the direct update() approach is faster.

classmethod get(*query, **filters)

Parameters

•   query -- Zero or more Expression objects.

•   filters -- Mapping of field-name to value for Django-style filter.

Raises
    DoesNotExist

Returns
    Model instance matching the specified filters.

    Retrieve a single model instance matching the given filters. If no model is returned, a DoesNotExist is raised.

        user = User.get(User.username == username, User.active == True)

    This method is also exposed via the SelectQuery, though it takes no parameters:

        active = User.select().where(User.active == True)
        try:
            user = (active
                    .where(
                        (User.username == username) &
                        (User.active == True))
                    .get())
        except User.DoesNotExist:
            user = None

    Note: The get() method is shorthand for selecting with a limit of 1. It has the added behavior of raising an exception when no matching row is found. If more than one row is found, the first row returned by the database cursor will be used.

classmethod get_or_none(*query, **filters)
    Identical to Model.get() but returns None if no model matches the given filters.

        active = User.select().where(User.active == True)
        user = (active
                .where(
                    (User.username == username) &
                    (User.active == True))
                .get_or_none())

classmethod get_by_id(pk)

Parameters
    pk -- Primary-key value.

    Short-hand for calling Model.get() specifying a lookup by primary key. Raises a DoesNotExist if instance with the given primary key value does not exist.

    Example:

        user = User.get_by_id(1)  # Returns user with id = 1.

classmethod set_by_id(key, value)

Parameters

•   key -- Primary-key value.

•   value (dict) -- Mapping of field to value to update.

    Short-hand for updating the data with the given primary-key. If no row exists with the given primary key, no exception will be raised.

    Example:

        # Set "is_admin" to True on user with id=3.
        User.set_by_id(3, {'is_admin': True})

classmethod delete_by_id(pk)

Parameters
    pk -- Primary-key value.

    Short-hand for deleting the row with the given primary-key. If no row exists with the given primary key, no exception will be raised.

classmethod get_or_create(**kwargs)

Parameters

•   kwargs -- Mapping of field-name to value.

•   defaults -- Default values to use if creating a new row.

Returns
    Tuple of Model instance and boolean indicating if a new object was created.

    Attempt to get the row matching the given filters. If no matching row is found, create a new row.

    Warning: Race-conditions are possible when using this method.

    Example without get_or_create:

        # Without `get_or_create`, we might write:
        try:
            person = Person.get(
                (Person.first_name == 'John') &
                (Person.last_name == 'Lennon'))
        except Person.DoesNotExist:
            person = Person.create(
                first_name='John',
                last_name='Lennon',
                birthday=datetime.date(1940, 10, 9))

    Equivalent code using get_or_create:

        person, created = Person.get_or_create(
            first_name='John',
            last_name='Lennon',
            defaults={'birthday': datetime.date(1940, 10, 9)})

classmethod filter(*dq_nodes, **filters)

Parameters

•   dq_nodes -- Zero or more DQ objects.

•   filters -- Django-style filters.

Returns
    ModelSelect query.

get_id()

Returns
    The primary-key of the model instance.

save(force_insert=False, only=None)

Parameters

•   force_insert (bool) -- Force INSERT query.

•   only (list) -- Only save the given Field instances.

Returns
    Number of rows modified.

    Save the data in the model instance. By default, the presence of a primary-key value will cause an UPDATE query to be executed.

    Example showing saving a model instance:

        user = User()
        user.username = 'some-user'  # does not touch the database
        user.save()  # change is persisted to the db

    When a model uses any primary key OTHER than an auto-incrementing integer it is necessary to specify force_insert=True when calling save() with a new instance:

        class Tag(Model):
            tag = TextField(primary_key=True)
        t = Tag(tag='python')
        t.save(force_insert=True)
        # create() automatically specifies force_insert=True:
        t = Tag.create(tag='sqlite')

dirty_fields
    Return list of fields that have been modified.

Return type
    list

    Note: If you just want to persist modified fields, you can call model.save(only=model.dirty_fields).

    To always save a model's dirty fields, use the Meta option only_save_dirty = True. Any calls to Model.save() will only save the dirty fields by default:

        class Person(Model):
            first_name = CharField()
            last_name = CharField()
            dob = DateField()
            class Meta:
                database = db
                only_save_dirty = True

    Warning: Peewee determines whether a field is "dirty" by observing when the field attribute is set on a model instance. If the field contains a value that is mutable, such as a dictionary instance, and that dictionary is then modified, Peewee will not notice the change.

    Warning: Do not do membership tests on this list, e.g. f in dirty_fields because if there is one or more fields in the dirty fields list, the field equality override will return a truthy Expression object. If you want to test if a field is dirty, instead check f.name in model.dirty_field_names.

dirty_field_names
    Return list of field names that have been modified.

Return type
    list

is_dirty()
    Return boolean indicating whether any fields were manually set.

delete_instance(recursive=False, delete_nullable=False)

Parameters

•   recursive (bool) -- Delete related models.

•   delete_nullable (bool) -- Delete related models that have a null foreign key. If False nullable relations will be set to NULL.

    Delete the given instance. Any foreign keys set to cascade on delete will be deleted automatically. For more programmatic control, you can specify recursive=True, which will delete any non-nullable related models (those that are nullable will be set to NULL). If you wish to delete all dependencies regardless of whether they are nullable, set delete_nullable=True.

    Example:

        some_obj.delete_instance()

    See Deleting Records <#deleting-records> for additional discussion.

classmethod bind(database, bind_refs=True, bind_backrefs=True)

Parameters

•   database (Database <#Database>) -- database to bind to.

•   bind_refs (bool) -- Bind related models.

•   bind_backrefs (bool) -- Bind back-reference related models.

    Bind the model (and specified relations) to the given database.

    See also: Database.bind().

classmethod bind_ctx(database, bind_refs=True, bind_backrefs=True)
    Like bind(), but returns a context manager that only binds the models for the duration of the wrapped block.

    See also: Database.bind_ctx().

classmethod table_exists()

Returns
    boolean indicating whether the table exists.

classmethod create_table(safe=True, **options)

Parameters
    safe (bool) -- When True, the create table query will include an IF NOT EXISTS clause.

    Create the model table, indexes, constraints and sequences.

    Example:

        with database:
            SomeModel.create_table()

classmethod drop_table(safe=True, **options)

Parameters
    safe (bool) -- If set to True, the drop table query will include an IF EXISTS clause.

    Drop the model table.

truncate_table(restart_identity=False, cascade=False)

Parameters

•   restart_identity (bool) -- Restart the id sequence (postgresql-only).

•   cascade (bool) -- Truncate related tables as well (postgresql-only).

    Truncate (delete all rows) for the model.

classmethod index(*fields, unique=False, safe=True, where=None, using=None, name=None)

Parameters

•   fields -- Fields to index.

•   unique (bool) -- Whether index is UNIQUE.

•   safe (bool) -- Whether to add IF NOT EXISTS clause.

•   where (Expression <#Expression>) -- Optional WHERE clause for index.

•   using (str) -- Index algorithm.

•   name (str) -- Optional index name.

    Expressive method for declaring an index on a model. Wraps the declaration of a ModelIndex instance.

    Examples:

        class Article(Model):
            name = TextField()
            timestamp = TimestampField()
            status = IntegerField()
            flags = BitField()
            is_sticky = flags.flag(1)
            is_favorite = flags.flag(2)
        # CREATE INDEX ... ON "article" ("name", "timestamp" DESC)
        idx = Article.index(Article.name, Article.timestamp.desc())
        # Be sure to add the index to the model:
        Article.add_index(idx)
        # CREATE UNIQUE INDEX ... ON "article" ("timestamp" DESC, "flags" & 2)
        # WHERE ("status" = 1)
        idx = (Article
               .index(Article.timestamp.desc(),
                      Article.flags.bin_and(2),
                      unique=True)
               .where(Article.status == 1))
        # Add index to model:
        Article.add_index(idx)

classmethod add_index(*args, **kwargs)

Parameters

•   args -- a ModelIndex instance, Field(s) to index, or a SQL instance that contains the SQL for creating the index.

•   kwargs -- Keyword arguments passed to ModelIndex constructor.

    Add an index to the model's definition.

    Note: This method does not actually create the index in the database. Rather, it adds the index definition to the model's metadata, so that a subsequent call to create_table() will create the new index (along with the table).

    Examples:

        class Article(Model):
            name = TextField()
            timestamp = TimestampField()
            status = IntegerField()
            flags = BitField()
            is_sticky = flags.flag(1)
            is_favorite = flags.flag(2)
        # CREATE INDEX ... ON "article" ("name", "timestamp") WHERE "status" = 1
        idx = Article.index(Article.name, Article.timestamp).where(Article.status == 1)
        Article.add_index(idx)
        # CREATE UNIQUE INDEX ... ON "article" ("timestamp" DESC, "flags" & 2)
        ts_flags_idx = Article.index(
            Article.timestamp.desc(),
            Article.flags.bin_and(2),
            unique=True)
        Article.add_index(ts_flags_idx)
        # You can also specify a list of fields and use the same keyword
        # arguments that the ModelIndex constructor accepts:
        Article.add_index(
            Article.name,
            Article.timestamp.desc(),
            where=(Article.status == 1))
        # Or even specify a SQL query directly:
        Article.add_index(SQL('CREATE INDEX ...'))

dependencies(search_nullable=False)

Parameters
    search_nullable (bool) -- Search models related via a nullable foreign key

Return type
    Generator expression yielding queries and foreign key fields.

    Generate a list of queries of dependent models. Yields a 2-tuple containing the query and corresponding foreign key field. Useful for searching dependencies of a model, i.e. things that would be orphaned in the event of a delete.

__iter__()

Returns
    a ModelSelect for the given class.

    Convenience function for iterating over all instances of a model.

    Example:

        Setting.insert_many([
            {'key': 'host', 'value': '192.168.1.2'},
            {'key': 'port': 'value': '1337'},
            {'key': 'user': 'value': 'nuggie'}]).execute()
        # Load settings from db into dict.
        settings = {setting.key: setting.value for setting in Setting}

__len__()

Returns
    Count of rows in table.

    Example:

        n_accounts = len(Account)
        # Equivalent:
        n_accounts = Account.select().count()

class ModelAlias(model, alias=None)

Parameters

•   model (Model <#Model>) -- Model class to reference.

•   alias (str) -- (optional) name for alias.

    Provide a separate reference to a model in a query.

    With Peewee, we use Model.alias() to alias a model class so it can be referenced twice in a single query:

        Owner = User.alias()
        query = (Favorite
                 .select(Favorite, Tweet.content, User.username, Owner.username)
                 .join_from(Favorite, Owner)  # Determine owner of favorite.
                 .join_from(Favorite, Tweet)  # Join favorite -> tweet.
                 .join_from(Tweet, User))     # Join tweet -> user.

    See Relationships and Joins <#relationships> for additional discussion.

class Metadata(model, database=None, table_name=None, indexes=None, primary_key=None, constraints=None, schema=None, only_save_dirty=False, depends_on=None, options=None, without_rowid=False, strict_tables=False, **kwargs)

Parameters

•   model (Model <#Model>) -- Model class.

•   database (Database <#Database>) -- database model is bound to.

•   table_name (str) -- Specify table name for model.

•   indexes (list) -- List of ModelIndex objects.

•   primary_key -- Primary key for model (only specified if this is a CompositeKey or False for no primary key.

•   constraints (list) -- List of table constraints.

•   schema (str) -- Schema table exists in.

•   only_save_dirty (bool) -- When save() is called, only save the fields which have been modified.

•   options (dict) -- Arbitrary options for the model.

•   without_rowid (bool) -- Specify WITHOUT ROWID (sqlite only).

•   strict_tables (bool) -- Specify STRICT (sqlite only, requires 3.37+).

•   kwargs -- Arbitrary setting attributes and values.

    Store metadata for a Model.

    This class should not be instantiated directly, but is instantiated using the attributes of a Model class' inner Meta class. Metadata attributes are then available on Model._meta.

    Example:

        class User(Model):
            ...
            class Meta:
                database = db
                table_name = 'user'
        # After class-creation, Meta configuration lives here:
        isinstance(User._meta, Metadata)  # True
        User._meta.database is db         # True
        User._meta.table_name == 'user'   # True

    See also: Model Settings <#model-options> for usage.

table
    Return a reference to the underlying Table object.

model_graph(refs=True, backrefs=True, depth_first=True)

Parameters

•   refs (bool) -- Follow foreign-key references.

•   backrefs (bool) -- Follow foreign-key back-references.

•   depth_first (bool) -- Do a depth-first search (False for breadth-first).

    Traverse the model graph and return a list of 3-tuples, consisting of (foreign key field, model class, is_backref).

set_database(database)

Parameters
    database (Database <#Database>) -- database object to bind Model to.

    Bind the model class to the given Database instance.

    Warning: This API should not need to be used. Instead, to change a Model database at run-time, use one of the following:

•   Model.bind()

•   Model.bind_ctx() (bind for scope of a context manager).

•   Database.bind()

•   Database.bind_ctx()

set_table_name(table_name)

Parameters
    table_name (str) -- table name to bind Model to.

    Bind the model class to the given table name at run-time.

class SubclassAwareMetadata
    Metadata subclass that tracks Model subclasses. Useful for when you need to track all models in a project.

    Example:

        from peewee import SubclassAwareMetadata
        class Base(Model):
            class Meta:
                database = db
                model_metadata_class = SubclassAwareMetadata
        # Create 3 model classes that inherit from Base.
        class A(Base): pass
        class B(Base): pass
        class C(Base): pass
        # Now let's make a helper for changing the `schema` for each Model.
        def change_schema(schema):
            def _update(model):
                model._meta.schema = schema
            return _update
        # Set all models to use "schema1", e.g. "schema1.a", "schema1.b", etc.
        # Will apply the function to every subclass of Base.
        Base._meta.map_models(change_schema('schema1'))
        # Set all models to use "schema2", e.g. "schema2.a", "schema2.b", etc.
        Base._meta.map_models(change_schema('schema2'))

map_models(fn)
    Apply a function to all subclasses.

    See also: ThreadSafeDatabaseMetadata <#playhouse.shortcuts.ThreadSafeDatabaseMetadata> for a Metadata subclass that supports changing the database attribute at run-time in a multi-threaded environment.

class ModelSelect(model, fields_or_models)

Parameters

•   model (Model <#Model>) -- Model class to select.

•   fields_or_models -- List of fields or model classes to select.

    Model-specific implementation of SELECT query.

switch(ctx=None)

Parameters
    ctx -- A Model, ModelAlias, subquery, or other object that was joined-on.

    Switch the join context - the source which subsequent calls to join() will be joined against. Used for specifying multiple joins against a single table.

    See Relationships and Joins <#relationships> for additional discussion.

    If the ctx is not given, then the query's model will be used.

    The following example selects from tweet and joins on both user and tweet-flag:

        sq = Tweet.select().join(User).switch(Tweet).join(TweetFlag)
        # Equivalent (since Tweet is the query's model)
        sq = Tweet.select().join(User).switch().join(TweetFlag)

objects(constructor=None)

Parameters
    constructor -- Constructor (defaults to returning model instances)

    Return result rows as objects created using the given constructor. The default behavior is to create model instances.

    Note: This method can be used, when selecting field data from multiple sources/models, to make all data available as attributes on the model being queried (as opposed to constructing the graph of joined model instances). For very complex queries this can have a positive performance impact, especially iterating large result sets.

    Similarly, you can use dicts(), tuples() or namedtuples() to achieve even more performance.

join(dest, join_type='INNER', on=None, src=None, attr=None)

Parameters

•   dest -- A Model, ModelAlias, Select query, or other object to join to.

•   join_type (str) -- Join type, defaults to INNER.

•   on -- Join predicate or a ForeignKeyField to join on.

•   src -- Explicitly specify the source of the join. If not specified then the current join context will be used.

•   attr (str) -- Attribute to use when projecting columns from the joined model.

    Join with another table-like object.

    Join type may be one of:

•   JOIN.INNER

•   JOIN.LEFT_OUTER

•   JOIN.RIGHT_OUTER

•   JOIN.FULL

•   JOIN.FULL_OUTER

•   JOIN.CROSS

    Example selecting tweets and joining on user in order to restrict to only those tweets made by "admin" users:

        sq = Tweet.select().join(User).where(User.is_admin == True)

    Example selecting users and joining on a particular foreign key field. See the example app <#example> for a real-life usage:

        sq = User.select().join(Relationship, on=Relationship.to_user)

    For an in-depth discussion of foreign-keys, joins and relationships between models, refer to Relationships and Joins <#relationships>.

join_from(src, dest, join_type='INNER', on=None, attr=None)

Parameters

•   src -- Source for join.

•   dest -- Table to join to.

    Use same parameter order as the non-model-specific join(). Bypasses the join context by requiring the join source to be specified.

filter(*args, **kwargs)

Parameters

•   args -- Zero or more DQ objects.

•   kwargs -- Django-style keyword-argument filters.

    Use Django-style filters to express a WHERE clause. Joins can be followed by chaining foreign-key fields. The supported operations are:

•   eq - equals

•   ne - not equals

•   lt, lte - less-than, less-than or equal-to

•   gt, gte - greater-than, greater-than or equal-to

•   in - IN set of values

•   is - IS (e.g. IS NULL).

•   like, ilike - LIKE and ILIKE (case-insensitive)

•   regexp - regular expression match

    Examples:

        # Get all tweets by user with username="peewee".
        q = Tweet.filter(user__username='peewee')
        # Get all posts that are draft or published, and written after 2023.
        q = Post.filter(
            (DQ(status='draft') | DQ(status='published')),
            timestamp__gte=datetime.date(2023, 1, 1))

prefetch(*subqueries, prefetch_type=PREFETCH_TYPE.WHERE)

Parameters

•   subqueries -- A list of Model classes or select queries to prefetch.

•   prefetch_type -- Query type to use for the subqueries.

Returns
    a list of models with selected relations prefetched.

    Execute the query, prefetching the given additional resources.

    Prefetch type may be one of:

•   PREFETCH_TYPE.WHERE

•   PREFETCH_TYPE.JOIN

    See also prefetch() standalone function.

    Example:

        # Fetch all Users and prefetch their associated tweets.
        query = User.select().prefetch(Tweet)
        for user in query:
            print(user.username)
            for tweet in user.tweets:
                print('  *', tweet.content)

    Note: Because prefetch must reconstruct a graph of models, it is necessary to be sure that the foreign-key/primary-key of any related models are selected, so that the related objects can be mapped correctly.

class DoesNotExist
    Base exception class raised when a call to Model.get() (or other .get() method) fails to return a matching result. Model classes have a model-specific subclass as a top-level attribute:

        def get_user(email):
            try:
                return User.get(fn.LOWER(User.email) == email.lower())
            except User.DoesNotExist:
                return None

class PeeweeException
    Base exception class for wrapped DB-API exceptions.

class ImproperlyConfigured
    Exception raised for configuration issues like missing drivers.

class DatabaseError

class DataError

class IntegrityError

class InterfaceError

class InternalError

class NotSupportedError

class OperationalError

class ProgrammingError
    Exception wrappers for DB-API errors raised by the driver. Allows users to catch the Peewee exception wrappers rather than the driver-specific types.

  Fields

class Field(null=False, index=False, unique=False, column_name=None, default=None, primary_key=False, constraints=None, sequence=None, collation=None, unindexed=False, choices=None, help_text=None, verbose_name=None, index_type=None)

Parameters

•   null (bool) -- Field allows NULLs.

•   index (bool) -- Create an index on field.

•   unique (bool) -- Create a unique index on field.

•   column_name (str) -- Specify column name for field.

•   default -- Default value (enforced in Python, not on server).

•   primary_key (bool) -- Field is the primary key.

•   constraints (list) -- List of constraints to apply to column, for example: [Check('price > 0')].

•   sequence (str) -- Sequence name for field.

•   collation (str) -- Collation name for field.

•   unindexed (bool) -- Declare field UNINDEXED (sqlite only).

•   choices (list) -- An iterable of 2-tuples mapping column values to display labels. Used for metadata purposes only, to help when displaying a dropdown of choices for field values, for example.

•   help_text (str) -- Help-text for field, metadata purposes only.

•   verbose_name (str) -- Verbose name for field, metadata purposes only.

•   index_type (str) -- Specify index type (postgres only), e.g. 'BRIN'.

    Fields on a Model are analogous to columns on a table.

field_type = '<some field type>'
    Attribute used to map this field to a column type, e.g. "INT". See the FIELD object in the source for more information.

column
    Retrieve a reference to the underlying Column object.

model
    The model the field is bound to.

name
    The name of the field.

db_value(value)
    Coerce a Python value into a value suitable for storage in the database. Sub-classes operating on special data-types will most likely want to override this method.

python_value(value)
    Coerce a value from the database into a Python object. Sub-classes operating on special data-types will most likely want to override this method.

coerce(value)
    This method is a shorthand that is used, by default, by both db_value() and python_value().

Parameters
    value -- arbitrary data from app or backend

Return type
    python data type

class IntegerField
    Field class for storing integers.

class BigIntegerField
    Field class for storing big integers (if supported by database).

class SmallIntegerField
    Field class for storing small integers (if supported by database).

class AutoField
    Field class for storing auto-incrementing primary keys.

    Note: In SQLite, for performance reasons, the default primary key type simply uses the max existing value + 1 for new values, as opposed to the max ever value + 1. This means deleted records can have their primary keys reused. In conjunction with SQLite having foreign keys disabled by default (meaning ON DELETE is ignored, even if you specify it explicitly), this can lead to surprising and dangerous behaviour. To avoid this, you may want to use one or both of AutoIncrementField and pragmas=[('foreign_keys', 'on')] when you instantiate SqliteDatabase.

class BigAutoField
    Field class for storing auto-incrementing primary keys using 64-bits.

class IdentityField(generate_always=False)

Parameters
    generate_always (bool) -- if specified, then the identity will always be generated (and specifying the value explicitly during INSERT will raise a programming error). Otherwise, the identity value is only generated as-needed.

    Field class for storing auto-incrementing primary keys using the Postgresql IDENTITY column type. The column definition ends up looking like this:

        id = IdentityField()
        # "id" INT GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY

    Attention: Requires Postgresql >= 10

class FloatField
    Field class for storing floating-point numbers.

class DoubleField
    Field class for storing double-precision floating-point numbers.

class DecimalField(max_digits=10, decimal_places=5, auto_round=False, rounding=None, **kwargs)

Parameters

•   max_digits (int) -- Maximum digits to store.

•   decimal_places (int) -- Maximum precision.

•   auto_round (bool) -- Automatically round values.

•   rounding --

    Defaults to decimal.DefaultContext.rounding.

    Field class for storing decimal numbers. Values are represented as decimal.Decimal objects.

class CharField(max_length=255)
    Field class for storing strings.

    Note: Values that exceed length are NOT truncated automatically.

class FixedCharField
    Field class for storing fixed-length strings.

    Note: Values that exceed length are not truncated automatically.

class TextField
    Field class for storing text.

class BlobField
    Field class for storing binary data.

class BitField
    Field class for storing options in a 64-bit integer column.

    Usage:

        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)
        >>> 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 use the flags on the Post class to build expressions in queries as well:

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

    When bulk-updating one or more bits in a BitField, you can use bitwise operators to set or clear one or more bits:

        # Set the 4th bit on all Post objects.
        Post.update(flags=Post.flags | 8).execute()
        # Clear the 1st and 3rd bits on all Post objects.
        Post.update(flags=Post.flags & ~(1 | 4)).execute()

    For simple operations, the flags provide handy set() and clear() methods for setting or clearing an individual bit:

        # Set the "is_deleted" bit on all posts.
        Post.update(flags=Post.is_deleted.set()).execute()
        # Clear the "is_deleted" bit on all posts.
        Post.update(flags=Post.is_deleted.clear()).execute()

flag(value=None)

Parameters
    value (int) -- Value associated with flag, typically a power of 2.

    Returns a descriptor that can get or set specific bits in the overall value. When accessed on the class itself, it returns a Expression object suitable for use in a query.

    If the value is not provided, it is assumed that each flag will be an increasing power of 2, so if you had four flags, they would have the values 1, 2, 4, 8.

class BigBitField
    Field class for storing arbitrarily-large bitmaps in a BLOB. The field will grow the underlying buffer as necessary, ensuring there are enough bytes of data to support the number of bits of data being stored.

    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)
        # BigBitField supports item accessor by bit-number, e.g.:
        assert bitmap.data[63]
        bitmap.data[0] = 1
        del bitmap.data[0]
        # We can also combine bitmaps using bitwise operators, e.g.
        b = Bitmap(data=b'\x01')
        b.data |= b'\x02'
        assert list(b.data) == [1, 1, 0, 0, 0, 0, 0, 0]
        assert len(b.data) == 1

clear()
    Clears the bitmap and sets length to 0.

set_bit(idx)

Parameters
    idx (int) -- Bit to set, indexed starting from zero.

    Sets the idx-th bit in the bitmap.

clear_bit(idx)

Parameters
    idx (int) -- Bit to clear, indexed starting from zero.

    Clears the idx-th bit in the bitmap.

toggle_bit(idx)

Parameters
    idx (int) -- Bit to toggle, indexed starting from zero.

Returns
    Whether the bit is set or not.

    Toggles the idx-th bit in the bitmap and returns whether the bit is set or not.

    Example:

        >>> bitmap = Bitmap()
        >>> bitmap.data.toggle_bit(10)  # Toggle the 10th bit.
        True
        >>> bitmap.data.toggle_bit(10)  # This will clear the 10th bit.
        False

is_set(idx)

Parameters
    idx (int) -- Bit index, indexed starting from zero.

Returns
    Whether the bit is set or not.

    Returns boolean indicating whether the idx-th bit is set or not.

__getitem__(idx)
    Same as is_set()

__setitem__(idx, value)
    Set the bit at idx to value (True or False).

__delitem__(idx)
    Same as clear_bit()

__len__()
    Return the length of the bitmap in bytes.

__iter__()
    Returns an iterator yielding 1 or 0 for each bit in the bitmap.

__and__(other)

Parameters
    other -- Either BigBitField, bytes, bytearray or memoryview object.

Returns
    bitwise and of two bitmaps.

__or__(other)

Parameters
    other -- Either BigBitField, bytes, bytearray or memoryview object.

Returns
    bitwise or of two bitmaps.

__xor__(other)

Parameters
    other -- Either BigBitField, bytes, bytearray or memoryview object.

Returns
    bitwise xor of two bitmaps.

class UUIDField
    Field class for storing uuid.UUID objects. With Postgres, the underlying column's data-type will be UUID. Since SQLite and MySQL do not have a native UUID type, the UUID is stored as a VARCHAR instead.

class BinaryUUIDField
    Field class for storing uuid.UUID objects efficiently in 16-bytes. Uses the database's BLOB data-type (or VARBINARY in MySQL, or BYTEA in Postgres).

class DateTimeField(formats=None, **kwargs)

Parameters
    formats (list) -- A list of format strings to use when coercing a string to a date-time.

    Field class for storing datetime.datetime objects.

    Accepts a special parameter formats, which contains a list of formats the datetime can be encoded with (for databases that do not have support for a native datetime data-type). The default supported formats are:

        '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
        '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
        '%Y-%m-%d' # year-month-day

    Note: SQLite does not have a native datetime data-type, so datetimes are stored as strings. This is handled transparently by Peewee, but if you have pre-existing data you should ensure it is stored as YYYY-mm-dd HH:MM:SS or one of the other supported formats.

year
    Reference the year of the value stored in the column in a query.

        Blog.select().where(Blog.pub_date.year == 2018)

month
    Reference the month of the value stored in the column in a query.

day Reference the day of the value stored in the column in a query.

hour
    Reference the hour of the value stored in the column in a query.

minute
    Reference the minute of the value stored in the column in a query.

second
    Reference the second of the value stored in the column in a query.

to_timestamp()
    Method that returns a database-specific function call that will allow you to work with the given date-time value as a numeric timestamp. This can sometimes simplify tasks like date math in a compatible way.

    Example:

        # Find all events that are exactly 1 hour long.
        query = (Event
                 .select()
                 .where((Event.start.to_timestamp() + 3600) ==
                        Event.stop.to_timestamp())
                 .order_by(Event.start))

truncate(date_part)

Parameters
    date_part (str) -- year, month, day, hour, minute or second.

Returns
    expression node to truncate date/time to given resolution.

    Truncates the value in the column to the given part. This method is useful for finding all rows within a given month, for instance.

class DateField(formats=None, **kwargs)

Parameters
    formats (list) -- A list of format strings to use when coercing a string to a date.

    Field class for storing datetime.date objects.

    Accepts a special parameter formats, which contains a list of formats the datetime can be encoded with (for databases that do not have support for a native date data-type). The default supported formats are:

        '%Y-%m-%d' # year-month-day
        '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second
        '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond

    Note: If the incoming value does not match a format, it is returned as-is.

year
    Reference the year of the value stored in the column in a query.

        Person.select().where(Person.dob.year == 1983)

month
    Reference the month of the value stored in the column in a query.

day Reference the day of the value stored in the column in a query.

to_timestamp()
    See DateTimeField.to_timestamp().

truncate(date_part)
    See DateTimeField.truncate(). Note that only year, month, and day are meaningful for DateField.

class TimeField(formats=None, **kwargs)

Parameters
    formats (list) -- A list of format strings to use when coercing a string to a time.

    Field class for storing datetime.time objects (not timedelta).

    Accepts a special parameter formats, which contains a list of formats the datetime can be encoded with (for databases that do not have support for a native time data-type). The default supported formats are:

        '%H:%M:%S.%f' # hour:minute:second.microsecond
        '%H:%M:%S' # hour:minute:second
        '%H:%M' # hour:minute
        '%Y-%m-%d %H:%M:%S.%f' # year-month-day hour-minute-second.microsecond
        '%Y-%m-%d %H:%M:%S' # year-month-day hour-minute-second

    Note: If the incoming value does not match a format, it is returned as-is.

hour
    Reference the hour of the value stored in the column in a query.

        evening_events = Event.select().where(Event.time.hour > 17)

minute
    Reference the minute of the value stored in the column in a query.

second
    Reference the second of the value stored in the column in a query.

class TimestampField(resolution=1, utc=False, **kwargs)

Parameters

•   resolution -- Can be provided as either a power of 10, or as an exponent indicating how many decimal places to store.

•   utc (bool) -- Treat timestamps as UTC.

    Field class for storing date-times as integer timestamps. Sub-second resolution is supported by multiplying by a power of 10 to get an integer.

    If the resolution parameter is 0 or 1, then the timestamp is stored using second resolution. A resolution between 2 and 6 is treated as the number of decimal places, e.g. resolution=3 corresponds to milliseconds. Alternatively, the decimal can be provided as a multiple of 10, such that resolution=10 will store 1/10th of a second resolution.

    The resolution parameter can be either 0-6 or 10, 100, etc up to 1000000 (for microsecond resolution). This allows sub-second precision while still using an IntegerField for storage. The default is second resolution.

    Also accepts a boolean parameter utc, used to indicate whether the timestamps should be UTC. Default is False.

    Finally, the field default is the current timestamp. If you do not want this behavior, then explicitly pass in default=None.

class IPField
    Field class for storing IPv4 addresses efficiently (as integers).

class BooleanField
    Field class for storing boolean values.

class BareField(coerce=None, **kwargs)

Parameters
    coerce -- Optional function to use for converting raw values into a specific format.

    Field class that does not specify a data-type (SQLite-only).

    Since data-types are not enforced, you can declare fields without any data-type. 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.

    Accepts a special coerce parameter, a function that takes a value coming from the database and converts it into the appropriate Python type.

class ForeignKeyField(model, field=None, backref=None, on_delete=None, on_update=None, deferrable=None, object_id_name=None, lazy_load=True, constraint_name=None, **kwargs)

Parameters

•   model (Model <#Model>) -- Model to reference or the string 'self' if declaring a self-referential foreign key.

•   field (Field <#Field>) -- Field to reference on model (default is primary key).

•   backref (str) -- Accessor name for back-reference, or "+" to disable the back-reference accessor.

•   on_delete (str) -- ON DELETE action, e.g. 'CASCADE'..

•   on_update (str) -- ON UPDATE action.

•   deferrable (str) -- Control when constraint is enforced, e.g. 'INITIALLY DEFERRED'.

•   object_id_name (str) -- Name for object-id accessor.

•   lazy_load (bool) -- Fetch the related object when the foreign-key field attribute is accessed (if it was not already loaded). If this is disabled, accessing the foreign-key field will return the value stored in the foreign-key column.

•   constraint_name (str) -- (optional) name to use for foreign-key constraint.

    Field class for storing a foreign key.

        class User(Model):
            name = TextField()
        class Tweet(Model):
            user = ForeignKeyField(User, backref='tweets')
            content = TextField()
        # "user" attribute
        >>> some_tweet.user
        <User: charlie>
        # "tweets" backref attribute
        >>> for tweet in charlie.tweets:
        ...     print(tweet.content)
        Some tweet
        Another tweet
        Yet another tweet

    For an in-depth discussion of foreign-keys, joins and relationships between models, refer to Relationships and Joins <#relationships>.

    Note: Foreign keys do not have a particular field_type as they will take their field type depending on the type of primary key on the model they are related to.

    Note: If you manually specify a field, that field must be either a primary key or have a unique constraint.

    Note: Take care with foreign keys in SQLite. By default, ON DELETE has no effect, which can have surprising (and usually unwanted) effects on your database integrity. This can affect you even if you don't specify on_delete, since the default ON DELETE behaviour (to fail without modifying your data) does not happen, and your data can be silently relinked. The safest thing to do is to specify pragmas={'foreign_keys': 1} when you instantiate SqliteDatabase.

class DeferredForeignKey(rel_model_name, **kwargs)

Parameters
    rel_model_name (str) -- Model name to reference.

    Field class for representing a deferred foreign key. Useful for circular foreign-key references, for example:

        class Husband(Model):
            name = TextField()
            wife = DeferredForeignKey('Wife', deferrable='INITIALLY DEFERRED')
        class Wife(Model):
            name = TextField()
            husband = ForeignKeyField(Husband, deferrable='INITIALLY DEFERRED')

    In the above example, when the Wife model is declared, the foreign-key Husband.wife is automatically resolved and turned into a regular ForeignKeyField.

    Warning: DeferredForeignKey references are resolved when model classes are declared and created. This means that if you declare a DeferredForeignKey to a model class that has already been imported and created, the deferred foreign key instance will never be resolved. For example:

        class User(Model):
            username = TextField()
        class Tweet(Model):
            # This will never actually be resolved, because the User
            # model has already been declared.
            user = DeferredForeignKey('user', backref='tweets')
            content = TextField()

    In cases like these you should use the regular ForeignKeyField or you can manually resolve deferred foreign keys like so:

        # Tweet.user will be resolved into a ForeignKeyField:
        DeferredForeignKey.resolve(User)

class ManyToManyField(model, backref=None, through_model=None, on_delete=None, on_update=None)

Parameters

•   model (Model <#Model>) -- Model to create relationship with.

•   backref (str) -- Accessor name for back-reference

•   through_model (Model <#Model>) -- Model to use for the intermediary table. If not provided, a simple through table will be automatically created.

•   on_delete (str) -- ON DELETE action, e.g. 'CASCADE'. Will be used for foreign-keys in through model.

•   on_update (str) -- ON UPDATE action. Will be used for foreign-keys in through model.

    The ManyToManyField provides a simple interface for working with many-to-many relationships, inspired by Django. A many-to-many relationship is typically implemented by creating a junction table with foreign keys to the two models being related. For instance, if you were building a syllabus manager for college students, the relationship between students and courses would be many-to-many. Here is the schema using standard APIs:

    Attention: This is not a field in the sense that there is no column associated with it. Rather, it provides a convenient interface for accessing rows of data related via a through model.

    Standard way of declaring a many-to-many relationship (without the use of the ManyToManyField):

        class Student(Model):
            name = CharField()
        class Course(Model):
            name = CharField()
        class StudentCourse(Model):
            student = ForeignKeyField(Student)
            course = ForeignKeyField(Course)

    To query the courses for a particular student, you would join through the junction table:

        # List the courses that "Huey" is enrolled in:
        courses = (Course
                   .select()
                   .join(StudentCourse)
                   .join(Student)
                   .where(Student.name == 'Huey'))
        for course in courses:
            print(course.name)

    The ManyToManyField is designed to simplify this use-case by providing a field-like API for querying and modifying data in the junction table. Here is how our code looks using ManyToManyField:

        class Student(Model):
            name = CharField()
        class Course(Model):
            name = CharField()
            students = ManyToManyField(Student, backref='courses')

    Note: It does not matter from Peewee's perspective which model the ManyToManyField goes on, since the back-reference is just the mirror image. In order to write valid Python, though, you will need to add the ManyToManyField on the second model so that the name of the first model is in the scope.

    We still need a junction table to store the relationships between students and courses. This model can be accessed by calling the get_through_model() method. This is useful when creating tables.

        # Create tables for the students, courses, and relationships between
        # the two.
        db.create_tables([
            Student,
            Course,
            Course.students.get_through_model()])

    When accessed from a model instance, the ManyToManyField exposes a ModelSelect representing the set of related objects. Let's use the interactive shell to see how all this works:

        >>> huey = Student.get(Student.name == 'huey')
        >>> [course.name for course in huey.courses]
        ['English 101', 'CS 101']
        >>> engl_101 = Course.get(Course.name == 'English 101')
        >>> [student.name for student in engl_101.students]
        ['Huey', 'Mickey', 'Zaizee']

    To add new relationships between objects, you can either assign the objects directly to the ManyToManyField attribute, or call the add() method. The difference between the two is that simply assigning will clear out any existing relationships, whereas add() can preserve existing relationships.

        >>> huey.courses = Course.select().where(Course.name.contains('english'))
        >>> for course in huey.courses.order_by(Course.name):
        ...     print(course.name)
        English 101
        English 151
        English 201
        English 221
        >>> cs_101 = Course.get(Course.name == 'CS 101')
        >>> cs_151 = Course.get(Course.name == 'CS 151')
        >>> huey.courses.add([cs_101, cs_151])
        >>> [course.name for course in huey.courses.order_by(Course.name)]
        ['CS 101', 'CS151', 'English 101', 'English 151', 'English 201',
         'English 221']

    This is quite a few courses, so let's remove the 200-level english courses. To remove objects, use the remove() method.

        >>> huey.courses.remove(Course.select().where(Course.name.contains('2'))
        2
        >>> [course.name for course in huey.courses.order_by(Course.name)]
        ['CS 101', 'CS151', 'English 101', 'English 151']

    To remove all relationships from a collection, you can use the clear() method. Let's say that English 101 is canceled, so we need to remove all the students from it:

        >>> engl_101 = Course.get(Course.name == 'English 101')
        >>> engl_101.students.clear()

    Note: For an overview of implementing many-to-many relationships using standard Peewee APIs, check out the Many-to-Many Relationships <#manytomany> section. For all but the most simple cases, you will be better off implementing many-to-many using the standard APIs.

through_model
    The Model representing the many-to-many junction table. Will be auto-generated if not explicitly declared.

add(value, clear_existing=True)

Parameters

•   value -- Either a Model instance, a list of model instances, or a SelectQuery.

•   clear_existing (bool) -- Whether to remove existing relationships.

    Associate value with the current instance. You can pass in a single model instance, a list of model instances, or even a ModelSelect.

    Example code:

        # Huey needs to enroll in a bunch of courses, including all
        # the English classes, and a couple Comp-Sci classes.
        huey = Student.get(Student.name == 'Huey')
        # We can add all the objects represented by a query.
        english_courses = Course.select().where(
            Course.name.contains('english'))
        huey.courses.add(english_courses)
        # We can also add lists of individual objects.
        cs101 = Course.get(Course.name == 'CS 101')
        cs151 = Course.get(Course.name == 'CS 151')
        huey.courses.add([cs101, cs151])

remove(value)

Parameters
    value -- Either a Model instance, a list of model instances, or a ModelSelect.

    Disassociate value from the current instance. Like add(), you can pass in a model instance, a list of model instances, or even a ModelSelect.

    Example code:

        # Huey is currently enrolled in a lot of english classes
        # as well as some Comp-Sci. He is changing majors, so we
        # will remove all his courses.
        english_courses = Course.select().where(
            Course.name.contains('english'))
        huey.courses.remove(english_courses)
        # Remove the two Comp-Sci classes Huey is enrolled in.
        cs101 = Course.get(Course.name == 'CS 101')
        cs151 = Course.get(Course.name == 'CS 151')
        huey.courses.remove([cs101, cs151])

clear()
    Remove all associated objects.

    Example code:

        # English 101 is canceled this semester, so remove all
        # the enrollments.
        english_101 = Course.get(Course.name == 'English 101')
        english_101.students.clear()

get_through_model()
    Return the Model representing the many-to-many junction table. This can be specified manually when the field is being instantiated using the through_model parameter. If a through_model is not specified, one will automatically be created.

    When creating tables for an application that uses ManyToManyField, you must explicitly create the through table.

        # Get a reference to the automatically-created through table.
        StudentCourseThrough = Course.students.get_through_model()
        # Create tables for our two models as well as the through model.
        db.create_tables([
            Student,
            Course,
            StudentCourseThrough])

class DeferredThroughModel
    Place-holder for a through-model in cases where, due to a dependency, you cannot declare either a model or a many-to-many field without introducing NameErrors.

    Example:

        class Note(BaseModel):
            content = TextField()
        NoteThroughDeferred = DeferredThroughModel()
        class User(BaseModel):
            username = TextField()
            notes = ManyToManyField(Note, through_model=NoteThroughDeferred)
        # Cannot declare this before "User" since it has a foreign-key to
        # the User model.
        class NoteThrough(BaseModel):
            note = ForeignKeyField(Note)
            user = ForeignKeyField(User)
        # Resolve dependencies.
        NoteThroughDeferred.set_model(NoteThrough)

class CompositeKey(*field_names)

Parameters
    field_names -- Names of fields that comprise the primary key.

    A primary key composed of multiple columns. Unlike the other fields, a composite key is defined in the model's Meta class after the fields have been defined. It takes as parameters the string names of the fields to use as the primary key:

        class BlogTagThrough(Model):
            blog = ForeignKeyField(Blog, backref='tags')
            tag = ForeignKeyField(Tag, backref='blogs')
            class Meta:
                primary_key = CompositeKey('blog', 'tag')

    See Composite primary keys <#composite-keys> for additional discussion.

  Schema Manager

class SchemaManager(model, database=None, **context_options)

Parameters

•   model (Model <#Model>) -- Model class.

•   database (Database <#Database>) -- if None defaults to model._meta.database.

    Provides methods for managing the creation and deletion of tables and indexes for the given model.

create_table(safe=True, **options)

Parameters

•   safe (bool) -- Specify IF NOT EXISTS clause.

•   options -- Arbitrary options.

    Execute CREATE TABLE query for the given model.

drop_table(safe=True, drop_sequences=True, **options)

Parameters

•   safe (bool) -- Specify IF EXISTS clause.

•   drop_sequences (bool) -- Drop any sequences associated with the columns on the table (postgres only).

•   options -- Arbitrary options.

    Execute DROP TABLE query for the given model.

truncate_table(restart_identity=False, cascade=False)

Parameters

•   restart_identity (bool) -- Restart the id sequence (postgres-only).

•   cascade (bool) -- Truncate related tables as well (postgres-only).

    Execute TRUNCATE TABLE for the given model. If the database is Sqlite, which does not support TRUNCATE, then an equivalent DELETE query will be executed.

create_indexes(safe=True)

Parameters
    safe (bool) -- Specify IF NOT EXISTS clause.

    Execute CREATE INDEX queries for the indexes defined for the model.

drop_indexes(safe=True)

Parameters
    safe (bool) -- Specify IF EXISTS clause.

    Execute DROP INDEX queries for the indexes defined for the model.

create_sequence(field)

Parameters
    field (Field <#Field>) -- Field instance which specifies a sequence.

    Create sequence for the given Field.

drop_sequence(field)

Parameters
    field (Field <#Field>) -- Field instance which specifies a sequence.

    Drop sequence for the given Field.

create_foreign_key(field)

Parameters
    field (ForeignKeyField <#ForeignKeyField>) -- Foreign-key field constraint to add.

    Add a foreign-key constraint for the given field. This method should not be necessary in most cases, as foreign-key constraints are created as part of table creation. The exception is when you are creating a circular foreign-key relationship using DeferredForeignKey. In those cases, it is necessary to first create the tables, then add the constraint for the deferred foreign-key:

        class Language(Model):
            name = TextField()
            selected_snippet = DeferredForeignKey('Snippet')
        class Snippet(Model):
            code = TextField()
            language = ForeignKeyField(Language, backref='snippets')
        # Creates both tables but does not create the constraint for the
        # Language.selected_snippet foreign key (because of the circular
        # dependency).
        db.create_tables([Language, Snippet])
        # Explicitly create the constraint:
        Language._schema.create_foreign_key(Language.selected_snippet)

    For more information, see documentation on Circular foreign key dependencies <#circular-fks>.

    Warning: Because SQLite has limited support for altering existing tables, it is not possible to add a foreign-key constraint to an existing SQLite table.

create_all(safe=True, **table_options)

Parameters
    safe (bool) -- Whether to specify IF NOT EXISTS.

    Create sequence(s), index(es) and table for the model.

drop_all(safe=True, drop_sequences=True, **options)

Parameters

•   safe (bool) -- Whether to specify IF EXISTS.

•   drop_sequences (bool) -- Drop any sequences associated with the columns on the table (postgres only).

•   options -- Arbitrary options.

    Drop table for the model and associated indexes.

class Index(name, table, expressions, unique=False, safe=False, where=None, using=None, nulls_distinct=None)

Parameters

•   name (str) -- Index name.

•   table (Table <#Table>) -- Table to create index on.

•   expressions -- List of columns to index on (or expressions).

•   unique (bool) -- Whether index is UNIQUE.

•   safe (bool) -- Whether to add IF NOT EXISTS clause.

•   where (Expression <#Expression>) -- Optional WHERE clause for index.

•   using (str) -- Index algorithm.

•   nulls_distinct (bool) -- Postgres-only - specify True (NULLS DISTINCT) or False (NULLS NOT DISTINCT) - controls handling of NULL in unique indexes.

safe(_safe=True)

Parameters
    _safe (bool) -- Whether to add IF NOT EXISTS clause.

where(*expressions)

Parameters
    expressions -- zero or more expressions to include in the WHERE clause.

    Include the given expressions in the WHERE clause of the index. The expressions will be AND-ed together with any previously-specified WHERE expressions.

using(_using=None)

Parameters
    _using (str) -- Specify index algorithm for USING clause.

nulls_distinct(nulls_distinct=None)

Parameters
    nulls_distinct (bool) -- specify True (NULLS DISTINCT) or False for (NULLS NOT DISTINCT).

    Requires Postgres 15 or newer.

    Control handling of NULL values in unique indexes.

class ModelIndex(model, fields, unique=False, safe=True, where=None, using=None, name=None, nulls_distinct=None)

Parameters

•   model (Model <#Model>) -- Model class to create index on.

•   fields (list) -- Fields to index.

•   unique (bool) -- Whether index is UNIQUE.

•   safe (bool) -- Whether to add IF NOT EXISTS clause.

•   where (Expression <#Expression>) -- Optional WHERE clause for index.

•   using (str) -- Index algorithm or type, e.g. 'BRIN', 'GiST' or 'GIN'.

•   name (str) -- Optional index name.

•   nulls_distinct (bool) -- Postgres-only - specify True (NULLS DISTINCT) or False (NULLS NOT DISTINCT) - controls handling of NULL in unique indexes.

    Expressive method for declaring an index on a model.

    Examples:

        class Article(Model):
            name = TextField()
            timestamp = TimestampField()
            status = IntegerField()
            flags = BitField()
            is_sticky = flags.flag(1)
            is_favorite = flags.flag(2)
        # CREATE INDEX ... ON "article" ("name", "timestamp")
        idx = ModelIndex(Article, (Article.name, Article.timestamp))
        # CREATE INDEX ... ON "article" ("name", "timestamp") WHERE "status" = 1
        idx = idx.where(Article.status == 1)
        # CREATE UNIQUE INDEX ... ON "article" ("timestamp" DESC, "flags" & 2) WHERE "status" = 1
        idx = ModelIndex(
            Article,
            (Article.timestamp.desc(), Article.flags.bin_and(2)),
            unique = True).where(Article.status == 1)

    You can also use Model.index():

        idx = Article.index(Article.name, Article.timestamp).where(Article.status == 1)

    To add an index to a model definition use Model.add_index():

        idx = Article.index(Article.name, Article.timestamp).where(Article.status == 1)
        # Add above index definition to the model definition. When you call
        # Article.create_table() (or database.create_tables([Article])), the
        # index will be created.
        Article.add_index(idx)

  Query-builder

class Node
    Base-class for all components which make up the AST for a SQL query.

static copy(method)
    Decorator to use with Node methods that mutate the node's state. This allows method-chaining, e.g.:

        query = MyModel.select()
        new_query = query.where(MyModel.field == 'value')

unwrap()
    API for recursively unwrapping "wrapped" nodes. Base case is to return self.

is_alias()
    API for determining if a node, at any point, has been explicitly aliased by the user.

class Source(alias=None)
    A source of row tuples, for example a table, join, or select query. By default provides a "magic" attribute named "c" that is a factory for column/attribute lookups, for example:

        User = Table('users')
        query = (User
                 .select(User.c.username)
                 .where(User.c.active == True)
                 .order_by(User.c.username))

alias(name)
    Returns a copy of the object with the given alias applied.

select(*columns)

Parameters
    columns -- Column instances, expressions, functions, sub-queries, or anything else that you would like to select.

    Create a Select query on the table. If the table explicitly declares columns and no columns are provided, then by default all the table's defined columns will be selected.

join(dest, join_type='INNER', on=None)

Parameters

•   dest (Source <#Source>) -- Join the table with the given destination.

•   join_type (str) -- Join type.

•   on -- Expression to use as join predicate.

Returns
    a Join instance.

    Join type may be one of:

•   JOIN.INNER

•   JOIN.LEFT_OUTER

•   JOIN.RIGHT_OUTER

•   JOIN.FULL

•   JOIN.FULL_OUTER

•   JOIN.CROSS

left_outer_join(dest, on=None)

Parameters

•   dest (Source <#Source>) -- Join the table with the given destination.

•   on -- Expression to use as join predicate.

Returns
    a Join instance.

    Convenience method for calling join() using a LEFT OUTER join.

class BaseTable
    Base class for table-like objects, which support JOINs via operator overloading.

__and__(dest)
    Perform an INNER join on dest.

__add__(dest)
    Perform a LEFT OUTER join on dest.

__sub__(dest)
    Perform a RIGHT OUTER join on dest.

__or__(dest)
    Perform a FULL OUTER join on dest.

__mul__(dest)
    Perform a CROSS join on dest.

class Table(name, columns=None, primary_key=None, schema=None, alias=None)
    Represents a table in the database (or a table-like object such as a view).

Parameters

•   name (str) -- Database table name

•   columns (tuple) -- List of column names (optional).

•   primary_key (str) -- Name of primary key column.

•   schema (str) -- Schema name used to access table (if necessary).

•   alias (str) -- Alias to use for table in SQL queries.

    Note: If columns are specified, the magic "c" attribute will be disabled.

    When columns are not explicitly defined, tables have a special attribute "c" which is a factory that provides access to table columns dynamically.

    Example:

        User = Table('users')
        query = (User
                 .select(User.c.id, User.c.username)
                 .order_by(User.c.username))

    Equivalent example when columns are specified:

        User = Table('users', ('id', 'username'))
        query = (User
                 .select(User.id, User.username)
                 .order_by(User.username))

bind(database=None)

Parameters
    database -- Database object.

    Bind this table to the given database (or unbind by leaving empty).

    When a table is bound to a database, queries may be executed against it without the need to specify the database in the query's execute method.

bind_ctx(database=None)

Parameters
    database -- Database object.

    Return a context manager that will bind the table to the given database for the duration of the wrapped block.

select(*columns)

Parameters
    columns -- Column instances, expressions, functions, sub-queries, or anything else that you would like to select.

    Create a Select query on the table. If the table explicitly declares columns and no columns are provided, then by default all the table's defined columns will be selected.

    Examples:

        User = Table('users', ('id', 'username'))
        # Because columns were defined on the Table, we will default to
        # selecting both of the User table's columns.
        # Evaluates to SELECT id, username FROM users
        query = User.select()
        Note = Table('notes')
        query = (Note
                 .select(Note.c.content, Note.c.timestamp, User.username)
                 .join(User, on=(Note.c.user_id == User.id))
                 .where(Note.c.is_published == True)
                 .order_by(Note.c.timestamp.desc()))
        # Using a function to select users and the number of notes they
        # have authored.
        query = (User
                 .select(
                    User.username,
                    fn.COUNT(Note.c.id).alias('n_notes'))
                 .join(
                    Note,
                    JOIN.LEFT_OUTER,
                    on=(User.id == Note.c.user_id))
                 .order_by(fn.COUNT(Note.c.id).desc()))

insert(insert=None, columns=None, **kwargs)

Parameters

•   insert -- A dictionary mapping column to value, an iterable that yields dictionaries (i.e. list), or a Select query.

•   columns (list) -- The list of columns to insert into when the data being inserted is not a dictionary.

•   kwargs -- Mapping of column-name to value.

    Create a Insert query into the table.

replace(insert=None, columns=None, **kwargs)

Parameters

•   insert -- A dictionary mapping column to value, an iterable that yields dictionaries (i.e. list), or a Select query.

•   columns (list) -- The list of columns to insert into when the data being inserted is not a dictionary.

•   kwargs -- Mapping of column-name to value.

    Create a Insert query into the table whose conflict resolution method is to replace.

update(update=None, **kwargs)

Parameters

•   update -- A dictionary mapping column to value.

•   kwargs -- Mapping of column-name to value.

    Create a Update query for the table.

delete()
    Create a Delete query for the table.

class Join(lhs, rhs, join_type=JOIN.INNER, on=None, alias=None)
    Represent a JOIN between two table-like objects.

Parameters

•   lhs -- Left-hand side of the join.

•   rhs -- Right-hand side of the join.

•   join_type -- Type of join. e.g. JOIN.INNER, JOIN.LEFT_OUTER, etc.

•   on -- Expression describing the join predicate.

•   alias (str) -- Alias to apply to joined data.

on(predicate)

Parameters
    predicate (Expression <#Expression>) -- join predicate.

    Specify the predicate expression used for this join.

class ValuesList(values, columns=None, alias=None)
    Represent a values list that can be used like a table.

Parameters

•   values -- a list-of-lists containing the row data to represent.

•   columns (list) -- the names to give to the columns in each row.

•   alias (str) -- alias to use for values-list.

    Example:

        data = [(1, 'first'), (2, 'second')]
        vl = ValuesList(data, columns=('idx', 'name'))
        query = (vl
                 .select(vl.c.idx, vl.c.name)
                 .order_by(vl.c.idx))
        # Yields:
        # SELECT t1.idx, t1.name
        # FROM (VALUES (1, 'first'), (2, 'second')) AS t1(idx, name)
        # ORDER BY t1.idx

columns(*names)

Parameters
    names -- names to apply to the columns of data.

    Example:

        vl = ValuesList([(1, 'first'), (2, 'second')])
        vl = vl.columns('idx', 'name').alias('v')
        query = vl.select(vl.c.idx, vl.c.name)
        # Yields:
        # SELECT v.idx, v.name
        # FROM (VALUES (1, 'first'), (2, 'second')) AS v(idx, name)

class CTE(name, query, recursive=False, columns=None)
    Represent a common-table-expression. For example queries, see Common Table Expressions <#cte>.

Parameters

•   name -- Name for the CTE.

•   query -- Select query describing CTE.

•   recursive (bool) -- Whether the CTE is recursive.

•   columns (list) -- Explicit list of columns produced by CTE (optional).

select_from(*columns)
    Create a SELECT query that utilizes the given common table expression as the source for a new query.

Parameters
    columns -- One or more columns to select from the CTE.

Returns
    Select query utilizing the common table expression

union_all(other)
    Used on the base-case CTE to construct the recursive term of the CTE.

Parameters
    other -- recursive term, generally a Select query.

Returns
    a recursive CTE with the given recursive term.

class ColumnBase
    Base-class for column-like objects, attributes or expressions.

    Column-like objects can be composed using various operators and special methods.

•   &: Logical AND

•   |: Logical OR

•   +: Addition

•   -: Subtraction

•   *: Multiplication

•   /: Division

•   ^: Exclusive-OR

•   ==: Equality

•   !=: Inequality

•   >: Greater-than

•   <: Less-than

•   >=: Greater-than or equal

•   <=: Less-than or equal

•   <<: IN

•   >>: IS (i.e. IS NULL)

•   %: LIKE

•   **: ILIKE

•   bin_and(): Binary AND

•   bin_or(): Binary OR

•   in_(): IN

•   not_in(): NOT IN

•   regexp(): REGEXP

•   is_null(True/False): IS NULL or IS NOT NULL

•   contains(s): LIKE %s%

•   startswith(s): LIKE s%

•   endswith(s): LIKE %s

•   between(low, high): BETWEEN low AND high

•   concat(): ||

alias(alias)

Parameters
    alias (str) -- Alias for the given column-like object.

Returns
    a Alias object.

    Indicate the alias that should be given to the specified column-like object.

cast(as_type)

Parameters
    as_type (str) -- Type name to cast to.

Returns
    a Cast object.

    Create a CAST expression.

asc(collation=None, nulls=None)

Parameters

•   collation (str) -- Collation name to use for sorting.

•   nulls (str) -- Sort nulls (FIRST or LAST).

Returns
    an ascending Ordering object for the column.

desc(collation=None, nulls=None)

Parameters

•   collation (str) -- Collation name to use for sorting.

•   nulls (str) -- Sort nulls (FIRST or LAST).

Returns
    an descending Ordering object for the column.

__invert__()

Returns
    a Negated wrapper for the column.

class Column(source, name)

Parameters

•   source (Source <#Source>) -- Source for column.

•   name (str) -- Column name.

    Column on a table or a column returned by a sub-query.

class Alias(node, alias)

Parameters

•   node (Node <#Node>) -- a column-like object.

•   alias (str) -- alias to assign to column.

    Create a named alias for the given column-like object.

alias(alias=None)

Parameters
    alias (str) -- new name (or None) for aliased column.

    Create a new Alias for the aliased column-like object. If the new alias is None, then the original column-like object is returned.

class Negated(node)
    Represents a negated column-like object.

class Value(value, converter=None, unpack=True)

Parameters

•   value -- Python object or scalar value.

•   converter -- Function used to convert value into type the database understands.

•   unpack (bool) -- Whether lists or tuples should be unpacked into a list of values or treated as-is.

    Value to be used in a parameterized query. It is the responsibility of the caller to ensure that the value passed in can be adapted to a type the database driver understands.

AsIs(value, converter=None)
    Represents a Value that is treated as-is, and passed directly back to the database driver. This may be useful if you are using database extensions that accept native Python data-types and you do not wish Peewee to impose any handling of the values.

    In the event a converter is in scope for this value, the converter will be applied unless converter=False (in which case no conversion is applied by Peewee and the value is passed directly to the driver). The Postgres JSON extensions make use of this to pass dict and list to the driver, which then handles the JSON serialization more efficiently, for example.

class Cast(node, cast)

Parameters

•   node -- A column-like object.

•   cast (str) -- Type to cast to.

    Represents a CAST(<node> AS <cast>) expression.

class Ordering(node, direction, collation=None, nulls=None)

Parameters

•   node -- A column-like object.

•   direction (str) -- ASC or DESC

•   collation (str) -- Collation name to use for sorting.

•   nulls (str) -- Sort nulls (FIRST or LAST).

    Represent ordering by a column-like object.

    Postgresql supports a non-standard clause ("NULLS FIRST/LAST"). Peewee will automatically use an equivalent CASE statement for databases that do not support this (Sqlite / MySQL).

collate(collation=None)

Parameters
    collation (str) -- Collation name to use for sorting.

Asc(node, collation=None, nulls=None)
    Short-hand for instantiating an ascending Ordering object.

Desc(node, collation=None, nulls=None)
    Short-hand for instantiating an descending Ordering object.

class Expression(lhs, op, rhs, flat=True)

Parameters

•   lhs -- Left-hand side.

•   op -- Operation.

•   rhs -- Right-hand side.

•   flat (bool) -- Whether to wrap expression in parentheses.

    Represent a binary expression of the form (lhs op rhs), e.g. (foo + 1).

class Entity(*path)

Parameters
    path -- Components that make up the dotted-path of the entity name.

    Represent a quoted entity in a query, such as a table, column, alias. The name may consist of multiple components, e.g. "a_table"."column_name".

__getattr__(self, attr)
    Factory method for creating sub-entities.

class SQL(sql, params=None)

Parameters

•   sql (str) -- SQL query string.

•   params (tuple) -- Parameters for query (optional).

    Represent a parameterized SQL query or query-fragment.

Check(constraint, name=None)

Parameters

•   constraint (str) -- Constraint SQL.

•   name (str) -- constraint name.

    Represent a CHECK constraint.

    Warning: MySQL may not support a name parameter when inlining the constraint along with the column definition. The solution is to just put the named Check constraint in the model's Meta.constraints list instead of in the field instances constraints=[...] list.

Default(value)

Parameters
    value -- default value (literal).

    Represent a DEFAULT constraint. It is important to note that this constraint does not accept a parameterized value, so the value literal must be given. If a string value is intended, it must be quoted.

    Examples:

        # "added" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP.
        added = DateTimeField(constraints=[Default('CURRENT_TIMESTAMP')])
        # "label" TEXT NOT NULL DEFAULT 'string literal'
        label = TextField(constraints=[Default("'string literal'")])
        # "status" INTEGER NOT NULL DEFAULT 0
        status = IntegerField(constraints=[Default(0)])

class Function(name, arguments, coerce=True, python_value=None)

Parameters

•   name (str) -- Function name.

•   arguments (tuple) -- Arguments to function.

•   coerce (bool) -- Whether to coerce the function result to a particular data-type when reading function return values from the cursor.

•   python_value (callable) -- Function to use for converting the return value from the cursor.

    Represent an arbitrary SQL function call.

    Note: Rather than instantiating this class directly, it is recommended to use the fn helper.

    Example of using fn to call an arbitrary SQL function:

        # Query users and count of tweets authored.
        query = (User
                 .select(User.username, fn.COUNT(Tweet.id).alias('ct'))
                 .join(Tweet, JOIN.LEFT_OUTER, on=(User.id == Tweet.user_id))
                 .group_by(User.username)
                 .order_by(fn.COUNT(Tweet.id).desc()))

over(partition_by=None, order_by=None, start=None, end=None, window=None, exclude=None)

Parameters

•   partition_by (list) -- List of columns to partition by.

•   order_by (list) -- List of columns / expressions to order window by.

•   start -- A SQL instance or a string expressing the start of the window range.

•   end -- A SQL instance or a string expressing the end of the window range.

•   frame_type (str) -- Window.RANGE, Window.ROWS or Window.GROUPS.

•   window (Window <#Window>) -- A Window instance.

•   exclude -- Frame exclusion, one of Window.CURRENT_ROW, Window.GROUP, Window.TIES or Window.NO_OTHERS.

    Note: For an in-depth guide to using window functions with Peewee, see the Window Functions <#window-functions> section.

    Examples:

        # Using a simple partition on a single column.
        query = (Sample
                 .select(
                    Sample.counter,
                    Sample.value,
                    fn.AVG(Sample.value).over([Sample.counter]))
                 .order_by(Sample.counter))
        # Equivalent example Using a Window() instance instead.
        window = Window(partition_by=[Sample.counter])
        query = (Sample
                 .select(
                    Sample.counter,
                    Sample.value,
                    fn.AVG(Sample.value).over(window))
                 .window(window)  # Note call to ".window()"
                 .order_by(Sample.counter))
        # Example using bounded window.
        query = (Sample
                 .select(Sample.value,
                         fn.SUM(Sample.value).over(
                            partition_by=[Sample.counter],
                            start=Window.CURRENT_ROW,  # current row
                            end=Window.following()))  # unbounded following
                 .order_by(Sample.id))

filter(where)

Parameters
    where -- Expression for filtering aggregate.

    Add a FILTER (WHERE...) clause to an aggregate function. The where expression is evaluated to determine which rows are fed to the aggregate function. This SQL feature is supported for Postgres and SQLite.

coerce(coerce=True)

Parameters
    coerce (bool) -- Whether to attempt to coerce function-call result to a Python data-type.

    When coerce is True, the target data-type is inferred using several heuristics. Read the source for BaseModelCursorWrapper._initialize_columns method to see how this works.

python_value(func=None)

Parameters
    python_value (callable) -- Function to use for converting the return value from the cursor.

    Specify a particular function to use when converting values returned by the database cursor. For example:

        # Get user and a list of their tweet IDs. The tweet IDs are
        # returned as a comma-separated string by the db, so we'll split
        # the result string and convert the values to python ints.
        convert_ids = lambda s: [int(i) for i in (s or '').split(',') if i]
        tweet_ids = (fn
                     .GROUP_CONCAT(Tweet.id)
                     .python_value(convert_ids))
        query = (User
                 .select(User.username, tweet_ids.alias('tweet_ids'))
                 .join(Tweet)
                 .group_by(User.username))
        for user in query:
            print(user.username, user.tweet_ids)
        # e.g.,
        # huey [1, 4, 5, 7]
        # mickey [2, 3, 6]
        # zaizee []

fn()
    The fn() helper is actually an instance of Function that implements a __getattr__ hook to provide a nice API for calling SQL functions.

    To create a node representative of a SQL function call, use the function name as an attribute on fn and then provide the arguments as you would if calling a Python function:

        # List users and the number of tweets they have authored,
        # from highest-to-lowest:
        sql_count = fn.COUNT(Tweet.id)
        query = (User
                 .select(User, sql_count.alias('count'))
                 .join(Tweet, JOIN.LEFT_OUTER)
                 .group_by(User)
                 .order_by(sql_count.desc()))
        # Get the timestamp of the most recent tweet:
        query = Tweet.select(fn.MAX(Tweet.timestamp))
        max_timestamp = query.scalar()  # Retrieve scalar result from query.

    Function calls can, like anything else, be composed and nested:

        # Get users whose username begins with "A" or "a":
        a_users = User.select().where(fn.LOWER(fn.SUBSTR(User.username, 1, 1)) == 'a')

class Window(partition_by=None, order_by=None, start=None, end=None, frame_type=None, extends=None, exclude=None, alias=None)

Parameters

•   partition_by (list) -- List of columns to partition by.

•   order_by (list) -- List of columns to order by.

•   start -- A SQL instance or a string expressing the start of the window range.

•   end -- A SQL instance or a string expressing the end of the window range.

•   frame_type (str) -- Window.RANGE, Window.ROWS or Window.GROUPS.

•   extends -- A Window definition to extend. Alternately, you may specify the window's alias instead.

•   exclude -- Frame exclusion, one of Window.CURRENT_ROW, Window.GROUP, Window.TIES or Window.NO_OTHERS.

•   alias (str) -- Alias for the window.

    Represent a WINDOW clause.

    Note: For an in-depth guide to using window functions with Peewee, see the Window Functions <#window-functions> section.

RANGE

ROWS

GROUPS
    Specify the window frame_type. See Frame Types: RANGE vs ROWS vs GROUPS <#window-frame-types>.

CURRENT_ROW
    Reference to current row for use in start/end clause or the frame exclusion parameter.

NO_OTHERS

GROUP

TIES
    Specify the window frame exclusion parameter.

static preceding(value=None)

Parameters
    value -- Number of rows preceding. If None is UNBOUNDED.

    Convenience method for generating SQL suitable for passing in as the start parameter for a window range.

static following(value=None)

Parameters
    value -- Number of rows following. If None is UNBOUNDED.

    Convenience method for generating SQL suitable for passing in as the end parameter for a window range.

as_rows()

as_range()

as_groups()
    Specify the frame type.

extends(window=None)

Parameters
    window (Window <#Window>) -- A Window definition to extend. Alternately, you may specify the window's alias instead.

exclude(frame_exclusion=None)

Parameters
    frame_exclusion -- Frame exclusion, one of Window.CURRENT_ROW, Window.GROUP, Window.TIES or Window.NO_OTHERS.

alias(alias=None)

Parameters
    alias (str) -- Alias to use for window.

Case(predicate, expression_tuples, default=None)

Parameters

•   predicate -- Predicate for CASE query (optional).

•   expression_tuples -- One or more cases to evaluate.

•   default -- Default value (optional).

Returns
    Representation of CASE statement.

    Example:

        Number = Table('numbers', ('val',))
        num_as_str = Case(Number.val, (
            (1, 'one'),
            (2, 'two'),
            (3, 'three')), 'a lot')
        query = Number.select(Number.val, num_as_str.alias('num_str'))

    Equivalent SQL:

        SELECT "val",
          CASE "val"
              WHEN 1 THEN 'one'
              WHEN 2 THEN 'two'
              WHEN 3 THEN 'three'
              ELSE 'a lot' END AS "num_str"
        FROM "numbers"

    Example:

        num_as_str = Case(None, (
            (Number.val == 1, 'one'),
            (Number.val == 2, 'two'),
            (Number.val == 3, 'three')), 'a lot')
        query = Number.select(Number.val, num_as_str.alias('num_str'))

    Equivalent SQL:

        SELECT "val",
          CASE
              WHEN "val" = 1 THEN 'one'
              WHEN "val" = 2 THEN 'two'
              WHEN "val" = 3 THEN 'three'
              ELSE 'a lot' END AS "num_str"
        FROM "numbers"

class NodeList(nodes, glue=' ', parens=False)

Parameters

•   nodes (list) -- Zero or more nodes.

•   glue (str) -- How to join the nodes when converting to SQL.

•   parens (bool) -- Whether to wrap the resulting SQL in parentheses.

    Represent a list of nodes, a multi-part clause, a list of parameters, etc.

CommaNodeList(nodes)

Parameters
    nodes (list) -- Zero or more nodes.

Returns
    a NodeList

    Represent a list of nodes joined by commas.

EnclosedNodeList(nodes)

Parameters
    nodes (list) -- Zero or more nodes.

Returns
    a NodeList

    Represent a list of nodes joined by commas and wrapped in parentheses.

class DQ(**query)

Parameters
    query -- Arbitrary filter expressions using Django-style lookups.

    Represent a composable Django-style filter expression suitable for use with the Model.filter() or ModelSelect.filter() methods.

class Tuple(*args)
    Represent a SQL row value <https://www.sqlite.org/rowvalue.html>. Row-values are supported by most databases.

class OnConflict(action=None, update=None, preserve=None, where=None, conflict_target=None, conflict_where=None, conflict_constraint=None)

Parameters

•   action (str) -- Action to take when resolving conflict.

•   update -- A dictionary mapping column to new value.

•   preserve -- A list of columns whose values should be preserved from the original INSERT. See also EXCLUDED.

•   where -- Expression to restrict the conflict resolution.

•   conflict_target -- Column(s) that comprise the constraint.

•   conflict_where -- Expressions needed to match the constraint target if it is a partial index (index with a WHERE clause).

•   conflict_constraint (str) -- Name of constraint to use for conflict resolution. Currently only supported by Postgres.

    Represent a conflict resolution clause for a data-modification query.

    See Upsert <#upsert> for detailed discussion.

preserve(*columns)

Parameters
    columns -- Columns whose values should be preserved.

update(_data=None, **kwargs)

Parameters

•   _data (dict) -- Dictionary mapping column to new value.

•   kwargs -- Dictionary mapping column name to new value.

    The update() method supports being called with either a dictionary of column-to-value, or keyword arguments representing the same.

where(*expressions)

Parameters
    expressions -- Expressions that restrict the action of the conflict resolution clause.

conflict_target(*constraints)

Parameters
    constraints -- Column(s) to use as target for conflict resolution.

conflict_where(*expressions)

Parameters
    expressions -- Expressions that match the conflict target index, in the case the conflict target is a partial index.

conflict_constraint(constraint)

Parameters
    constraint (str) -- Name of constraints to use as target for conflict resolution. Currently only supported by Postgres.

class EXCLUDED
    Helper object that exposes the EXCLUDED namespace that is used with INSERT ... ON CONFLICT to reference values in the conflicting data. This is a "magic" helper, such that one uses it by accessing attributes on it that correspond to a particular column.

    See Insert.on_conflict() for example usage.

  Queries

class BaseQuery
    The parent class from which all other query classes are derived. While you will not deal with BaseQuery directly in your code, it implements some methods that are common across all query types.

default_row_type = ROW.DICT

bind(database=None)

Parameters
    database (Database <#Database>) -- Database to execute query against.

    Bind the query to the given database for execution.

dicts(as_dict=True)

Parameters
    as_dict (bool) -- Specify whether to return rows as dictionaries.

    Return rows as dictionaries.

tuples(as_tuples=True)

Parameters
    as_tuples (bool) -- Specify whether to return rows as tuples.

    Return rows as tuples.

namedtuples(as_namedtuple=True)

Parameters
    as_namedtuple (bool) -- Specify whether to return rows as named tuples.

    Return rows as named tuples.

objects(constructor=None)

Parameters
    constructor -- Function that accepts row dict and returns an arbitrary object.

    Return rows as arbitrary objects using the given constructor.

sql()

Returns
    A 2-tuple consisting of the query's SQL and parameters.

execute(database)

Parameters
    database (Database <#Database>) -- Database to execute query against. Not required if query was previously bound to a database.

    Execute the query and return result (depends on type of query being executed). For example, select queries the return result will be an iterator over the query results.

iterator(database=None)

Parameters
    database (Database <#Database>) -- Database to execute query against. Not required if query was previously bound to a database.

    Execute the query and return an iterator over the result-set. For large result-sets this method is preferable as rows are not cached in-memory during iteration.

    Note: Because rows are not cached, the query may only be iterated over once. Subsequent iterations will return empty result-sets as the cursor will have been consumed.

    Example:

        query = StatTbl.select().order_by(StatTbl.timestamp).tuples()
        for row in query.iterator(db):
            process_row(row)

__iter__()
    Execute the query and return an iterator over the result-set.

    Unlike iterator(), this method will cause rows to be cached in order to allow efficient iteration, indexing and slicing.

__getitem__(value)

Parameters
    value -- Either an integer index or a slice.

    Retrieve a row or range of rows from the result-set.

__len__()
    Return the number of rows in the result-set.

    Warning: This does not issue a COUNT() query. Instead, the result-set is loaded as it would be during normal iteration, and the length is determined from the size of the result set.

class RawQuery(sql=None, params=None, **kwargs)

Parameters

•   sql (str) -- SQL query.

•   params (tuple) -- Parameters (optional).

    Create a query by directly specifying the SQL to execute.

class Query(where=None, order_by=None, limit=None, offset=None, **kwargs)

Parameters

•   where -- Representation of WHERE clause.

•   order_by (tuple) -- Columns or values to order by.

•   limit (int) -- Value of LIMIT clause.

•   offset (int) -- Value of OFFSET clause.

    Base-class for queries that support method-chaining APIs.

with_cte(*cte_list)

Parameters
    cte_list -- zero or more CTE objects.

    Include the given common-table expressions in the query. Any previously specified CTEs will be overwritten. For examples of common-table expressions, see Common Table Expressions <#cte>.

cte(name, recursive=False, columns=None)

Parameters

•   name (str) -- Alias for common table expression.

•   recursive (bool) -- Will this be a recursive CTE?

•   columns (list) -- List of column names (as strings).

    Indicate that a query will be used as a common table expression. For example, if we are modelling a category tree and are using a parent-link foreign key, we can retrieve all categories and their absolute depths using a recursive CTE:

        class Category(Model):
            name = TextField()
            parent = ForeignKeyField('self', backref='children', null=True)
        # The base case of our recursive CTE will be categories that are at
        # the root level -- in other words, categories without parents.
        roots = (Category
                 .select(Category.name, Value(0).alias('level'))
                 .where(Category.parent.is_null())
                 .cte(name='roots', recursive=True))
        # The recursive term will select the category name and increment
        # the depth, joining on the base term so that the recursive term
        # consists of all children of the base category.
        RTerm = Category.alias()
        recursive = (RTerm
                     .select(RTerm.name, (roots.c.level + 1).alias('level'))
                     .join(roots, on=(RTerm.parent == roots.c.id)))
        # Express <base term> UNION ALL <recursive term>.
        cte = roots.union_all(recursive)
        # Select name and level from the recursive CTE.
        query = (cte
                 .select_from(cte.c.name, cte.c.level)
                 .order_by(cte.c.name))
        for category in query:
            print(category.name, category.level)

    For more examples of CTEs, see Common Table Expressions <#cte>.

where(*expressions)

Parameters
    expressions -- zero or more expressions to include in the WHERE clause.

    Include the given expressions in the WHERE clause of the query. The expressions will be AND-ed together with any previously-specified WHERE expressions.

    Example selection users where the username is equal to 'somebody':

        sq = User.select().where(User.username == 'somebody')

    Example selecting tweets made by users who are either editors or administrators:

        sq = Tweet.select().join(User).where(
            (User.is_editor == True) |
            (User.is_admin == True))

    Example of deleting tweets by users who are no longer active:

        inactive_users = User.select().where(User.active == False)
        dq = (Tweet
              .delete()
              .where(Tweet.user.in_(inactive_users)))
        dq.execute()  # Return number of tweets deleted.

    Note: where() calls are chainable. Multiple calls will be "AND"-ed together.

orwhere(*expressions)

Parameters
    expressions -- zero or more expressions to include in the WHERE clause.

    Include the given expressions in the WHERE clause of the query. This method is the same as the Query.where() method, except that the expressions will be OR-ed together with any previously-specified WHERE expressions.

order_by(*values)

Parameters
    values -- zero or more Column-like objects to order by.

    Define the ORDER BY clause. Any previously-specified values will be overwritten.

order_by_extend(*values)

Parameters
    values -- zero or more Column-like objects to order by.

    Extend any previously-specified ORDER BY clause with the given values.

limit(value=None)

Parameters
    value (int) -- specify value for LIMIT clause.

offset(value=None)

Parameters
    value (int) -- specify value for OFFSET clause.

paginate(page, paginate_by=20)

Parameters

•   page (int) -- Page number of results (starting from 1).

•   paginate_by (int) -- Rows-per-page.

    Convenience method for specifying the LIMIT and OFFSET in a more intuitive way.

    This feature is designed with web-site pagination in mind, so the first page starts with page=1.

class SelectQuery
    Select query helper-class that implements operator-overloads for creating compound queries.

select_from(*columns)

Parameters
    columns -- one or more columns to select from the inner query.

Returns
    a new query that wraps the calling query.

    Create a new query that wraps the current (calling) query. For example, suppose you have a simple UNION query, and need to apply an aggregation on the union result-set. To do this, you need to write something like:

        SELECT "u"."owner", COUNT("u"."id") AS "ct"
        FROM (
            SELECT "id", "owner", ... FROM "cars"
            UNION
            SELECT "id", "owner", ... FROM "motorcycles"
            UNION
            SELECT "id", "owner", ... FROM "boats") AS "u"
        GROUP BY "u"."owner"

    The select_from() method is designed to simplify constructing this type of query.

    Example peewee code:

        class Car(Model):
            owner = ForeignKeyField(Owner, backref='cars')
            # ... car-specific fields, etc ...
        class Motorcycle(Model):
            owner = ForeignKeyField(Owner, backref='motorcycles')
            # ... motorcycle-specific fields, etc ...
        class Boat(Model):
            owner = ForeignKeyField(Owner, backref='boats')
            # ... boat-specific fields, etc ...
        cars = Car.select(Car.owner)
        motorcycles = Motorcycle.select(Motorcycle.owner)
        boats = Boat.select(Boat.owner)
        union = cars | motorcycles | boats
        query = (union
                 .select_from(union.c.owner, fn.COUNT(union.c.id))
                 .group_by(union.c.owner))

union_all(dest)
    Create a UNION ALL query with dest.

__add__(dest)
    Create a UNION ALL query with dest.

union(dest)
    Create a UNION query with dest.

__or__(dest)
    Create a UNION query with dest.

intersect(dest)
    Create an INTERSECT query with dest.

__and__(dest)
    Create an INTERSECT query with dest.

except_(dest)
    Create an EXCEPT query with dest. Note that the method name has a trailing "_" character since except is a Python reserved word.

__sub__(dest)
    Create an EXCEPT query with dest.

class SelectBase
    Base-class for Select and CompoundSelect queries.

peek(database, n=1)

Parameters

•   database (Database <#Database>) -- database to execute query against.

•   n (int) -- Number of rows to return.

Returns
    A single row if n = 1, else a list of rows.

    Execute the query and return the given number of rows from the start of the cursor. This function may be called multiple times safely, and will always return the first N rows of results.

first(database, n=1)

Parameters

•   database (Database <#Database>) -- database to execute query against.

•   n (int) -- Number of rows to return.

Returns
    A single row if n = 1, else a list of rows.

    Like the peek() method, except a LIMIT is applied to the query to ensure that only n rows are returned. Multiple calls for the same value of n will not result in multiple executions.

    The query is altered in-place so it is not possible to call first() and then later iterate over the full result-set using the same query object. Again, this is done to ensure that multiple calls to first() will not result in multiple query executions.

scalar(database, as_tuple=False, as_dict=False)

Parameters

•   database (Database <#Database>) -- database to execute query against.

•   as_tuple (bool) -- Return the result as a tuple?

•   as_dict (bool) -- Return the result as a dict?

Returns
    Single scalar value. If as_tuple = True, a row tuple is returned. If as_dict = True, a row dict is returned.

    Return a scalar value from the first row of results. If multiple scalar values are anticipated (e.g. multiple aggregations in a single query) then you may specify as_tuple=True to get the row tuple.

    Example:

        query = Note.select(fn.MAX(Note.timestamp))
        max_ts = query.scalar(db)
        query = Note.select(fn.MAX(Note.timestamp), fn.COUNT(Note.id))
        max_ts, n_notes = query.scalar(db, as_tuple=True)
        query = Note.select(fn.COUNT(Note.id).alias('count'))
        assert query.scalar(db, as_dict=True) == {'count': 123}

count(database, clear_limit=False)

Parameters

•   database (Database <#Database>) -- database to execute query against.

•   clear_limit (bool) -- Clear any LIMIT clause when counting.

Returns
    Number of rows in the query result-set.

    Return number of rows in the query result-set.

    Implemented by running SELECT COUNT(1) FROM (<current query>).

exists(database)

Parameters
    database (Database <#Database>) -- database to execute query against.

Returns
    Whether any results exist for the current query.

    Return a boolean indicating whether the current query has any results.

get(database)

Parameters
    database (Database <#Database>) -- database to execute query against.

Returns
    A single row from the database or None.

    Execute the query and return the first row, if it exists. Multiple calls will result in multiple queries being executed.

class CompoundSelectQuery(lhs, op, rhs)

Parameters

•   lhs (SelectBase <#SelectBase>) -- A Select or CompoundSelect query.

•   op (str) -- Operation (e.g. UNION, INTERSECT, EXCEPT).

•   rhs (SelectBase <#SelectBase>) -- A Select or CompoundSelect query.

    Class representing a compound SELECT query.

class Select(from_list=None, columns=None, group_by=None, having=None, distinct=None, windows=None, for_update=None, for_update_of=None, for_update_nowait=None, **kwargs)

Parameters

•   from_list (list) -- List of sources for FROM clause.

•   columns (list) -- Columns or values to select.

•   group_by (list) -- List of columns or values to group by.

•   having (Expression <#Expression>) -- Expression for HAVING clause.

•   distinct -- Either a boolean or a list of column-like objects.

•   windows (list) -- List of Window clauses.

•   for_update -- Boolean or str indicating if SELECT...FOR UPDATE.

•   for_update_of -- One or more tables for FOR UPDATE OF clause.

•   for_update_nowait (bool) -- Specify NOWAIT locking.

    Class representing a SELECT query.

    Note: Rather than instantiating this directly, most-commonly you will use a factory method like Table.select() or Model.select().

    Methods on the select query can be chained together.

    Example selecting some user instances from the database. Only the id and username columns are selected. When iterated, will return instances of the User model:

        query = User.select(User.id, User.username)
        for user in query:
            print(user.username)

    Example selecting users and additionally the number of tweets made by the user. The User instances returned will have an additional attribute, 'count', that corresponds to the number of tweets made:

        query = (User
                 .select(User, fn.COUNT(Tweet.id).alias('count'))
                 .join(Tweet, JOIN.LEFT_OUTER)
                 .group_by(User))
        for user in query:
            print(user.username, 'has tweeted', user.count, 'times')

    Note: While it is possible to instantiate Select directly, more commonly you will build the query using the method-chaining APIs.

columns(*columns)

Parameters
    columns -- Zero or more column-like objects to SELECT.

    Specify which columns or column-like values to SELECT.

select(*columns)

Parameters
    columns -- Zero or more column-like objects to SELECT.

    Same as Select.columns(), provided for backwards-compatibility.

select_extend(*columns)

Parameters
    columns -- Zero or more column-like objects to SELECT.

    Extend the current selection with the given columns.

    Example:

        def get_users(with_count=False):
            query = User.select()
            if with_count:
                query = (query
                         .select_extend(fn.COUNT(Tweet.id).alias('count'))
                         .join(Tweet, JOIN.LEFT_OUTER)
                         .group_by(User))
            return query

from_(*sources)

Parameters
    sources -- Zero or more sources for the FROM clause.

    Specify which table-like objects should be used in the FROM clause.

        User = Table('users')
        Tweet = Table('tweets')
        query = (User
                 .select(User.c.username, Tweet.c.content)
                 .from_(User, Tweet)
                 .where(User.c.id == Tweet.c.user_id))
        for row in query.execute(db):
            print(row['username'], '->', row['content'])

join(dest, join_type='INNER', on=None)

Parameters

•   dest -- A table or table-like object.

•   join_type (str) -- Type of JOIN, default is "INNER".

•   on (Expression <#Expression>) -- Join predicate.

    Join type may be one of:

•   JOIN.INNER

•   JOIN.LEFT_OUTER

•   JOIN.RIGHT_OUTER

•   JOIN.FULL

•   JOIN.FULL_OUTER

•   JOIN.CROSS

    Express a JOIN:

        User = Table('users', ('id', 'username'))
        Note = Table('notes', ('id', 'user_id', 'content'))
        query = (Note
                 .select(Note.content, User.username)
                 .join(User, on=(Note.user_id == User.id)))

group_by(*columns)

Parameters
    values -- zero or more Column-like objects to group by.

    Define the GROUP BY clause. Any previously-specified values will be overwritten.

    Additionally, to specify all columns on a given table, you can pass the table/model object in place of the individual columns.

    Example:

        query = (User
                 .select(User, fn.Count(Tweet.id).alias('count'))
                 .join(Tweet)
                 .group_by(User))

group_by_extend(*columns)

Parameters
    values -- zero or more Column-like objects to group by.

    Extend the GROUP BY clause with the given columns.

having(*expressions)

Parameters
    expressions -- zero or more expressions to include in the HAVING clause.

    Include the given expressions in the HAVING clause of the query. The expressions will be AND-ed together with any previously-specified HAVING expressions.

distinct(*columns)

Parameters
    columns -- Zero or more column-like objects.

    Indicate whether this query should use a DISTINCT clause. By specifying a single value of True the query will use a simple SELECT DISTINCT. Specifying one or more columns will result in a SELECT DISTINCT ON.

window(*windows)

Parameters
    windows -- zero or more Window objects.

    Define the WINDOW clause. Any previously-specified values will be overwritten.

    Example:

        # Equivalent example Using a Window() instance instead.
        window = Window(partition_by=[Sample.counter])
        query = (Sample
                 .select(
                    Sample.counter,
                    Sample.value,
                    fn.AVG(Sample.value).over(window))
                 .window(window)  # Note call to ".window()"
                 .order_by(Sample.counter))

for_update(for_update=True, of=None, nowait=None)

Parameters

•   for_update -- Either a boolean or a string indicating the desired expression, e.g. "FOR SHARE".

•   of -- One or more models to restrict locking to.

•   nowait (bool) -- Specify NOWAIT option when locking.

class _WriteQuery(table, returning=None, **kwargs)

Parameters

•   table (Table <#Table>) -- Table to write to.

•   returning (list) -- List of columns for RETURNING clause.

    Base-class for write queries.

returning(*returning)

Parameters
    returning -- Zero or more column-like objects for RETURNING clause

    Specify the RETURNING clause of query (Postgresql and Sqlite):

        query = (User
                 .insert_many([{'username': 'foo'},
                               {'username': 'bar'},
                               {'username': 'baz'}])
                 .returning(User.id, User.username)
                 .namedtuples())
        data = query.execute()
        for row in data:
            print('added:', row.username, 'with id=', row.id)

    See also: Returning Clause <#returning-clause> for additional discussion.

class Update(table, update=None, **kwargs)

Parameters

•   table (Table <#Table>) -- Table to update.

•   update (dict) -- Data to update.

    Class representing an UPDATE query.

    See Updating Records <#updating-records> for additional discussion.

    Example:

        PageView = Table('page_views')
        query = (PageView
                 .update({PageView.c.page_views: PageView.c.page_views + 1})
                 .where(PageView.c.url == url))
        query.execute(database)

from_(*sources)

Parameters
    sources (Source <#Source>) -- one or more Table, Model, query, or ValuesList to join with.

    Specify additional tables to join with using the UPDATE ... FROM syntax, which is supported by Postgres. The Postgres documentation <https://www.postgresql.org/docs/10/static/sql-update.html#id-1.9.3.176.8> provides additional detail, but to summarize: When a FROM clause is present, what essentially happens is that the target table is joined to the tables mentioned in the from_list, and each output row of the join represents an update operation for the target table. When using FROM you should ensure that the join produces at most one output row for each row to be modified.

    Example:

        # Update multiple users in a single query.
        data = [('huey', True),
                ('mickey', False),
                ('zaizee', True)]
        vl = ValuesList(data, columns=('username', 'is_admin'), alias='vl')
        # Here we'll update the "is_admin" status of the above users,
        # "joining" the VALUES() on the "username" column.
        query = (User
                 .update(is_admin=vl.c.is_admin)
                 .from_(vl)
                 .where(User.username == vl.c.username))

    The above query produces the following SQL:

        UPDATE "users" SET "is_admin" = "vl"."is_admin"
        FROM (
            VALUES ('huey', t), ('mickey', f), ('zaizee', t))
            AS "vl"("username", "is_admin")
        WHERE ("users"."username" = "vl"."username")

class Insert(table, insert=None, columns=None, on_conflict=None, **kwargs)

Parameters

•   table (Table <#Table>) -- Table to INSERT data into.

•   insert -- Either a dict, a list, or a query.

•   columns (list) -- List of columns when insert is a list or query.

•   on_conflict -- Conflict resolution strategy.

    Class representing an INSERT query.

    See Inserting Records <#inserting-records> for additional discussion.

    Example:

        User = Table('users')
        query = User.insert({User.c.username: 'huey'})
        query.execute(database)

as_rowcount(as_rowcount=True)

Parameters
    as_rowcount (bool) -- Whether to return the modified row count (as opposed to the last-inserted row id).

    SQLite and MySQL return the last inserted rowid. Postgresql will return a cursor for iterating over the inserted id(s).

    If you prefer to receive the inserted row-count, then specify as_rowcount():

        db = MySQLDatabase(...)
        query = User.insert_many([...])
        # By default, the last rowid is returned:
        #last_id = query.execute()
        # To get the modified row-count:
        rowcount = query.as_rowcount().execute()

on_conflict_ignore(ignore=True)

Parameters
    ignore (bool) -- Whether to add ON CONFLICT IGNORE clause.

    Specify IGNORE conflict resolution strategy.

on_conflict_replace(replace=True)

Parameters
    replace (bool) -- Whether to add ON CONFLICT REPLACE clause.

    Specify REPLACE conflict resolution strategy (SQLite and MySQL only).

on_conflict(action=None, update=None, preserve=None, where=None, conflict_target=None, conflict_where=None, conflict_constraint=None)

Parameters

•   action (str) -- Action to take when resolving conflict. If blank, action is assumed to be "update".

•   update -- A dictionary mapping column to new value.

•   preserve -- A list of columns whose values should be preserved from the original INSERT.

•   where -- Expression to restrict the conflict resolution.

•   conflict_target -- Column(s) that comprise the constraint.

•   conflict_where -- Expressions needed to match the constraint target if it is a partial index (index with a WHERE clause).

•   conflict_constraint (str) -- Name of constraint to use for conflict resolution. Currently only supported by Postgres.

    Specify the parameters for an OnConflict clause to use for conflict resolution.

    See Upsert <#upsert> for additional discussion.

    Examples:

        class User(Model):
            username = TextField(unique=True)
            last_login = DateTimeField(null=True)
            login_count = IntegerField()
        def log_user_in(username):
            now = datetime.datetime.now()
            # INSERT a new row for the user with the current timestamp and
            # login count set to 1. If the user already exists, then we
            # will preserve the last_login value from the "insert()" clause
            # and atomically increment the login-count.
            userid = (User
                      .insert(username=username, last_login=now, login_count=1)
                      .on_conflict(
                          conflict_target=[User.username],
                          preserve=[User.last_login],
                          update={User.login_count: User.login_count + 1})
                      .execute())
            return userid

    Example using the special EXCLUDED namespace:

        class KV(Model):
            key = CharField(unique=True)
            value = IntegerField()
        # Create one row.
        KV.create(key='k1', value=1)
        # Demonstrate usage of EXCLUDED.
        # Here we will attempt to insert a new value for a given key. If that
        # key already exists, then we will update its value with the *sum* of its
        # original value and the value we attempted to insert -- provided that
        # the new value is larger than the original value.
        query = (KV.insert(key='k1', value=10)
                 .on_conflict(conflict_target=[KV.key],
                              update={KV.value: KV.value + EXCLUDED.value},
                              where=(EXCLUDED.value > KV.value)))
        # Executing the above query will result in the following data being
        # present in the "kv" table:
        # (key='k1', value=11)
        query.execute()
        # If we attempted to execute the query *again*, then nothing would be
        # updated, as the new value (10) is now less than the value in the
        # original row (11).

class Delete
    Class representing a DELETE query.

    See Deleting Records <#deleting-records> for additional discussion.

    Example:

        Tweet = Table('tweets')
        # Delete all unpublished tweets older than 30 days.
        cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
        query = (Tweet
                 .delete()
                 .where(
                     (Tweet.c.is_published == False) &
                     (Tweet.c.timestamp < cutoff)))
        nrows = query.execute(database)

prefetch(sq, *subqueries, prefetch_type=PREFETCH_TYPE.WHERE)

Parameters

•   sq -- Query to use as starting-point.

•   subqueries -- One or more models or ModelSelect queries to eagerly fetch.

•   prefetch_type -- Query type to use for the subqueries.

Returns
    a list of models with selected relations prefetched.

    Eagerly fetch related objects, allowing efficient querying of multiple tables when a 1-to-many relationship exists. The prefetch type changes how the subqueries are constructed which may be desirable depending on the database engine in use.

    Prefetch type may be one of:

•   PREFETCH_TYPE.WHERE

•   PREFETCH_TYPE.JOIN

    See Relationships and Joins <#relationships> for in-depth discussion of joining and prefetch.

    For example, it is simple to query a many-to-1 relationship efficiently:

        query = (Tweet
                 .select(Tweet, User)
                 .join(User))
        for tweet in query:
            # Looking up tweet.user.username does not require a query since
            # the related user's columns were selected.
            print(tweet.user.username, '->', tweet.content)

    To efficiently do the inverse, query users and their tweets, you can use prefetch:

        query = User.select()
        for user in prefetch(query, Tweet):
            print(user.username)
            for tweet in user.tweets:  # Does not require additional query.
                print('    ', tweet.content)

    Note: Because prefetch must reconstruct a graph of models, it is necessary to be sure that the foreign-key/primary-key of any related models are selected, so that the related objects can be mapped correctly.

  Query-builder Internals

class AliasManager
    Manages the aliases assigned to Source objects in SELECT queries, so as to avoid ambiguous references when multiple sources are used in a single query.

add(source)
    Add a source to the AliasManager's internal registry at the current scope. The alias will be automatically generated using the following scheme (where each level of indentation refers to a new scope):

Parameters
    source (Source <#Source>) -- Make the manager aware of a new source. If the source has already been added, the call is a no-op.

get(source, any_depth=False)
    Return the alias for the source in the current scope. If the source does not have an alias, it will be given the next available alias.

Parameters
    source (Source <#Source>) -- The source whose alias should be retrieved.

Returns
    The alias already assigned to the source, or the next available alias.

Return type
    str

__setitem__(source, alias)
    Manually set the alias for the source at the current scope.

Parameters
    source (Source <#Source>) -- The source for which we set the alias.

push()
    Push a new scope onto the stack.

pop()
    Pop scope from the stack.

class State(scope, parentheses=False, subquery=False, **kwargs)
    Lightweight object for representing the state at a given scope. During SQL generation, each object visited by the Context can inspect the state. The State class allows Peewee to do things like:

•   Use a common interface for field types or SQL expressions, but use vendor-specific data-types or operators.

•   Compile a Column instance into a fully-qualified attribute, as a named alias, etc, depending on the value of the scope.

•   Ensure parentheses are used appropriately.

Parameters

•   scope (int) -- The scope rules to be applied while the state is active.

•   parentheses (bool) -- Wrap the contained SQL in parentheses.

•   subquery (bool) -- Whether the current state is a child of an outer query.

•   kwargs (dict) -- Arbitrary settings which should be applied in the current state.

class Context(**settings)
    Converts Peewee structures into parameterized SQL queries.

    Peewee structures should all implement a __sql__ method, which will be called by the Context class during SQL generation. The __sql__ method accepts a single parameter, the Context instance, which allows for recursive descent and introspection of scope and state.

scope
    Return the currently-active scope rules.

parentheses
    Return whether the current state is wrapped in parentheses.

subquery
    Return whether the current state is the child of another query.

scope_normal(**kwargs)
    The default scope. Sources are referred to by alias, columns by dotted-path from the source.

scope_source(**kwargs)
    Scope used when defining sources, e.g. in the column list and FROM clause of a SELECT query. This scope is used for defining the fully-qualified name of the source and assigning an alias.

scope_values(**kwargs)
    Scope used for UPDATE, INSERT or DELETE queries, where instead of referencing a source by an alias, we refer to it directly. Similarly, since there is a single table, columns do not need to be referenced by dotted-path.

scope_cte(**kwargs)
    Scope used when generating the contents of a common-table-expression. Used after a WITH statement, when generating the definition for a CTE (as opposed to merely a reference to one).

scope_column(**kwargs)
    Scope used when generating SQL for a column. Ensures that the column is rendered with its correct alias. Was needed because when referencing the inner projection of a sub-select, Peewee would render the full SELECT query as the "source" of the column (instead of the query's alias + . + column). This scope allows us to avoid rendering the full query when we only need the alias.

sql(obj)
    Append a composable Node object, sub-context, or other object to the query AST. Python values, such as integers, strings, floats, etc. are treated as parameterized values.

Returns
    The updated Context object.

literal(keyword)
    Append a string-literal to the current query AST.

Returns
    The updated Context object.

parse(node)

Parameters
    node (Node <#Node>) -- Instance of a Node subclass.

Returns
    a 2-tuple consisting of (sql, parameters).

    Convert the given node to a SQL AST and return a 2-tuple consisting of the SQL query and the parameters.

query()

Returns
    a 2-tuple consisting of (sql, parameters) for the context.

  Constants and Helpers

class Proxy
    Create a proxy or placeholder for another object.

initialize(obj)

Parameters
    obj -- Object to proxy to.

    Bind the proxy to the given object. Afterwards all attribute lookups and method calls on the proxy will be sent to the given object.

    Any callbacks that have been registered will be called.

attach_callback(callback)

Parameters
    callback -- A function that accepts a single parameter, the bound object.

Returns
    self

    Add a callback to be executed when the proxy is initialized.

class DatabaseProxy
    Proxy subclass that is suitable to use as a placeholder for a Database instance.

    See Initializing the Database <#initializing-database> for details.

    Example:

        db = DatabaseProxy()
        class BaseModel(Model):
            class Meta:
                database = db
        # ... some time later ...
        if app.config['DEBUG']:
            database = SqliteDatabase('local.db')
        elif app.config['TESTING']:
            database = SqliteDatabase(':memory:')
        else:
            database = PostgresqlDatabase('production')
        db.initialize(database)

chunked(iterable, n)

Parameters

•   iterable -- an iterable that is the source of the data to be chunked.

•   n (int) -- chunk size

Returns
    a new iterable that yields n-length chunks of the source data.

    Efficient implementation for breaking up large lists of data into smaller-sized chunks.

    Usage:

        it = range(10)  # An iterable that yields 0...9.
        # Break the iterable into chunks of length 4.
        for chunk in chunked(it, 4):
            print(', '.join(str(num) for num in chunk))
        # PRINTS:
        # 0, 1, 2, 3
        # 4, 5, 6, 7
        # 8, 9

class IndexMetadata
    Metadata for indexes returned by Database.get_indexes()

name

sql

columns

unique

table

class ColumnMetadata
    Metadata for columns returned by Database.get_columns()

name

data_type

null

primary_key

table

default

class ForeignKeyMetadata
    Metadata for foreign keys returned by Database.get_foreign_keys()

column

dest_table

dest_column

table

class ViewMetadata
    Metadata for views returned by Database.get_views()

name

sql

  Playhouse Reference
Module | Section
playhouse.sqlite_ext | SQLite <#sqlite>
playhouse.cysqlite_ext | CySqlite <#cysqlite-ext>
playhouse.sqliteq | SqliteQueueDatabase <#sqliteq>
playhouse.sqlite_udf | User-Defined Function Collection <#sqlite-udf>
playhouse.apsw_ext | APSW <#apsw>
playhouse.sqlcipher_ext | SQLCipher <#sqlcipher>
playhouse.postgres_ext | Postgresql <#postgresql>
playhouse.cockroachdb | CockroachDB <#crdb>
playhouse.mysql_ext | MySQL and MariaDB <#mysql>
playhouse.db_url | Database URLs <#db-url>
playhouse.pool | Connection Pooling <#pool>
playhouse.migrate | Schema Migrations <#migrate>
playhouse.reflection | Reflection <#reflection>
playhouse.test_utils | Test Utilities <#test-utils>
playhouse.fields | Extra Field Types <#extra-fields>
playhouse.shortcuts | Shortcuts <#shortcuts>
playhouse.hybrid | Hybrid Attributes <#hybrid>
playhouse.kv | Key/Value Store <#kv>
playhouse.signals | Signals <#signals>
playhouse.dataset | DataSet <#dataset>
playhouse.flask_utils | Flask Utilities <#flask-utils>

  SQLite

The core SqliteDatabase <#SqliteDatabase> handles pragmas, user-defined functions, WAL mode, full-text search and JSON. Because the full-text search and JSON fields are specific to SQLite, these features are provided by playhouse.sqlite_ext.

  On this page

•   Implementations

•   PRAGMA statements

•   User-Defined Functions

•   Locking Mode for Transactions

•   CySqlite

•   APSW

•   SQLCipher

•   SqliteQueueDatabase

•   SQLite-Specific Fields

•   SQLite JSON

•   Full-Text Search

•   User-Defined Function Collection

  Implementations

SqliteDatabase <#SqliteDatabase>
    Core SQLite implementation. Provides:

•   Pragma support (including WAL-mode)

•   User-defined functions

•   ATTACH / DETACH databases

•   Full-text search

•   JSON

    Full-text search and JSON implementations available in playhouse.sqlite_ext.

CySqliteDatabase (playhouse.cysqlite_ext)
    Extends SqliteDatabase <#SqliteDatabase>, uses cysqlite <https://cysqlite.readthedocs.io/en/latest/> driver.

•   All above functionality

•   Table-value functions

•   Commit / Rollback / Update / Progress / Trace hooks

•   BLOB I/O

•   Online backups

•   Can be built with encryption <https://cysqlite.readthedocs.io/en/latest/installation.html#sqlcipher>.

APSWDatabase (playhouse.apsw_ext)
    Extends SqliteDatabase <#SqliteDatabase>, uses apsw <https://github.com/rogerbinns/apsw/> driver.

    APSW is a thin C-level driver that exposes the full range of SQLite functionality.

SqlCipherDatabase (playhouse.sqlcipher_ext)
    Extends SqliteDatabase <#SqliteDatabase>, uses sqlcipher3 <https://github.com/coleifer/sqlcipher3> driver.

    SQLCipher provides transparent full-database encryption using 256-bit AES, ensuring data on-disk is secure.

SqliteQueueDatabase (playhouse.sqliteq)
    Extends SqliteDatabase <#SqliteDatabase>.

    Provides a SQLite database implementation with a long-lived background writer thread. All write operations are managed by a single write connection, preventing timeouts and database locking issues. This implementation is useful when using Sqlite in multi-threaded environments with frequent writes.

  PRAGMA statements

SQLite allows run-time configuration through PRAGMA statements (SQLite documentation <https://www.sqlite.org/pragma.html>). These statements are typically run when a new database connection is created.

To specify default PRAGMA statements for connections:

    db = SqliteDatabase('my_app.db', pragmas={
        'journal_mode': 'wal',
        'cache_size': 10000,  # 10000 pages, or ~40MB
        'foreign_keys': 1,  # Enforce foreign-key constraints
    })

PRAGMAs may also be configured dynamically using either the pragma() <#SqliteDatabase.pragma> method or the special properties exposed on the SqliteDatabase <#SqliteDatabase> object:

    # Set cache size to 64MB for *current connection*.
    db.pragma('cache_size', -64000)
    # Same as above.
    db.cache_size = -64000
    # Read the value of several pragmas:
    print('cache_size:', db.cache_size)
    print('foreign_keys:', db.foreign_keys)
    print('journal_mode:', db.journal_mode)
    print('page_size:', db.page_size)
    # Set foreign_keys pragma on current connection *AND* on all
    # connections opened subsequently.
    db.pragma('foreign_keys', 1, permanent=True)

Attention: Pragmas set using the pragma() <#SqliteDatabase.pragma> method are not re-applied when a new connection opens. To configure a pragma to be run whenever a new connection is opened, specify permanent=True.

    db.pragma('foreign_keys', 1, permanent=True)

See also: SQLite PRAGMA documentation: <https://sqlite.org/pragma.html>

  User-Defined Functions

SQLite can be extended with user-defined Python code. The SqliteDatabase <#SqliteDatabase> class supports a variety of user-defined extensions:

Functions
    User-defined functions accept any number of parameters and return a single value.

•   SqliteDatabase.register_function() <#SqliteDatabase.register_function>

•   SqliteDatabase.func() <#SqliteDatabase.func> - decorator.

Aggregates
    Aggregate values across multiple rows and return a single value.

•   SqliteDatabase.register_aggregate() <#SqliteDatabase.register_aggregate>

•   SqliteDatabase.aggregate() <#SqliteDatabase.aggregate> - decorator.

Window Functions
    Aggregates which support operating on sliding windows of data.

•   SqliteDatabase.register_window_function() <#SqliteDatabase.register_window_function>

•   SqliteDatabase.window_function() <#SqliteDatabase.window_function> - decorator.

Collations
    Control how values are ordered and sorted.

•   SqliteDatabase.register_collation() <#SqliteDatabase.register_collation>

•   SqliteDatabase.collation() <#SqliteDatabase.collation> - decorator.

Table Functions
    User-defined tables (requres cysqlite).

•   CySqliteDatabase.register_table_function()

•   CySqliteDatabase.table_function() - decorator.

Shared Libraries
    Load an extension from a shared library.

•   SqliteDatabase.load_extension() <#SqliteDatabase.load_extension>

•   SqliteDatabase.unload_extension() <#SqliteDatabase.unload_extension>

  Function example

    db = SqliteDatabase('analytics.db')
    from urllib.parse import urlparse
    @db.func('hostname')
    def hostname(url):
        if url is not None:
            return urlparse(url).netloc
    # Call this function in our code:
    # The following finds the most common hostnames of referrers by count:
    query = (PageView
             .select(fn.hostname(PageView.referrer), fn.COUNT(PageView.id))
             .group_by(fn.hostname(PageView.referrer))
             .order_by(fn.COUNT(PageView.id).desc()))

  Aggregate example

User-defined aggregates must define two methods:

•   step(*values) - called once for each row being aggregated.

•   finalize() - called only once to produce final aggregate value.

        from hashlib import md5
        @db.aggregate('md5')
        class MD5Checksum(object):
            def __init__(self):
                self.checksum = md5()
            def step(self, value):
                self.checksum.update(value.encode('utf-8'))
            def finalize(self):
                return self.checksum.hexdigest()
        # Usage:
        # The following computes an aggregate MD5 checksum for files broken
        # up into chunks and stored in the database.
        query = (FileChunk
                 .select(FileChunk.filename, fn.MD5(FileChunk.data))
                 .group_by(FileChunk.filename)
                 .order_by(FileChunk.filename, FileChunk.sequence))

  Window function example

User-defined window functions are simply aggregates with two additional methods:

•   step(*values) - called for each row being aggregated.

•   inverse(*values) - "invert" the effect of a call to step(*values).

•   value() - return the current value of the aggregate.

•   finalize() - return final aggregate value.

        # Window functions are normal aggregates with two additional methods:
        # inverse(value) - Perform the inverse of step(value).
        # value() - Report value at current step.
        @db.aggregate('mysum')
        class MySum(object):
            def __init__(self):
                self._value = 0
            def step(self, value):
                self._value += (value or 0)
            def inverse(self, value):
                self._value -= (value or 0)  # Do opposite of "step()".
            def value(self):
                return self._value
            def finalize(self):
                return self._value
        # e.g., aggregate sum of employee salaries over their department.
        query = (Employee
                 .select(
                     Employee.department,
                     Employee.salary,
                     fn.mysum(Employee.salary).over(
                         partition_by=[Employee.department]))
                 .order_by(Employee.id))

  Collation example

Collations accept two values and provide a value indicating how they should be ordered (e.g. cmp(lhs, rhs)).

    @db.collation('ireverse')
    def collate_reverse(s1, s2):
        # Case-insensitive reverse.
        s1, s2 = s1.lower(), s2.lower()
        return (s1 < s2) - (s1 > s2)  # Equivalent to -cmp(s1, s2)
    # To use this collation to sort books in reverse order...
    Book.select().order_by(collate_reverse.collation(Book.title))
    # Or...
    Book.select().order_by(Book.title.asc(collation='reverse'))

  Table function example

Example user-defined table-value function (see cysqlite TableFunction docs <https://cysqlite.readthedocs.io/en/latest/api.html#tablefunction> for full details on TableFunction).

    from cysqlite import TableFunction
    from playhouse.cysqlite_ext import CySqliteDatabase
    db = CySqliteDatabase('my_app.db')
    @db.table_function('series')
    class Series(TableFunction):
        columns = ['value']
        params = ['start', 'stop', 'step']
        def initialize(self, start=0, stop=None, step=1):
            """
            Table-functions declare an initialize() method, which is
            called with whatever arguments the user has called the
            function with.
            """
            self.start = self.current = start
            self.stop = stop or float('Inf')
            self.step = step
        def iterate(self, idx):
            """
            Iterate is called repeatedly by the SQLite database engine
            until the required number of rows has been read **or** the
            function raises a `StopIteration` signalling no more rows
            are available.
            """
            if self.current > self.stop:
                raise StopIteration
            ret, self.current = self.current, self.current + self.step
            return (ret,)
    # Usage:
    cursor = db.execute_sql('SELECT * FROM series(?, ?, ?)', (0, 5, 2))
    for value, in cursor:
        print(value)
    # Prints:
    # 0
    # 2
    # 4

  Shared Libraries

Example:

    # Load `closure.so` shared library in the current directory.
    db = SqliteDatabase('my_app.db')
    db.load_extension('closure')

Note: To support shared libraries, your SQLite3 will need to have been compiled with support for run-time loadable extensions.

  Locking Mode for Transactions

SQLite transactions can be opened in three different modes:

•   Deferred (default) - only acquires lock when a read or write is performed. The first read creates a shared lock <https://sqlite.org/lockingv3.html#locking> and the first write creates a reserved lock <https://sqlite.org/lockingv3.html#locking>. Because the acquisition of the lock is deferred until actually needed, it is possible that another thread or process could create a separate transaction and write to the database.

•   Immediate - a reserved lock <https://sqlite.org/lockingv3.html#locking> is acquired immediately. In this mode, no other connection may write to the database or open an immediate or exclusive transaction. Other processes can continue to read from the database, however.

•   Exclusive - opens an exclusive lock <https://sqlite.org/lockingv3.html#locking> which prevents all (except for read uncommitted) connections from accessing the database until the transaction is complete.

    Example specifying the locking mode:

        db = SqliteDatabase('app.db')
        with db.atomic('EXCLUSIVE'):
            read()
            write()
        @db.atomic('IMMEDIATE')
        def some_other_function():
            # This function is wrapped in an "IMMEDIATE" transaction.
            do_something_else()

    For more information, see the SQLite locking documentation <https://sqlite.org/lockingv3.html#locking>. To learn more about transactions in Peewee, see the Transactions <#transactions> documentation.

    Danger: Do not alter the isolation_level property of the sqlite3.Connection object. Peewee requires the sqlite3 driver be in autocommit-mode, which is handled automatically by SqliteDatabase <#SqliteDatabase>.

  CySqlite

CySqliteDatabase uses the cysqlite <https://cysqlite.readthedocs.io> driver, a high-performance alternative to the standard library sqlite3 module. cysqlite provides additional features and hooks not available with in the standard library sqlite3 driver.

Installation:

    pip install cysqlite

Usage:

    from playhouse.cysqlite_ext import CySqliteDatabase
    db = CySqliteDatabase('my_app.db', pragmas={
        'cache_size': -64000,
        'journal_mode': 'wal',
        'foreign_keys': 1,
    })

class CySqliteDatabase(database, **kwargs)

Parameters

•   pragmas (list) -- A list of 2-tuples containing pragma key and value to set every time a connection is opened.

•   timeout -- Set the busy-timeout on the SQLite driver (in seconds).

•   rank_functions (bool) -- Make search result ranking functions available. Recommended only when using FTS4.

•   regexp_function (bool) -- Make the REGEXP function available.

    See also: CySqliteDatabase extends SqliteDatabase <#SqliteDatabase> and inherits all methods for declaring user-defined functions, aggregates, window functions, collations, pragmas, etc.

    Example:

        db = CySqliteDatabase('app.db', pragmas={'journal_mode': 'wal'})

table_function(name)
    Class-decorator for registering a cysqlite.TableFunction. Table functions are user-defined functions that, rather than returning a single, scalar value, can return any number of rows of tabular data.

    See cysqlite docs <https://cysqlite.readthedocs.io/en/latest/api.html#tablefunction> for details on TableFunction API.

        from cysqlite import TableFunction
        @db.table_function('series')
        class Series(TableFunction):
            columns = ['value']
            params = ['start', 'stop', 'step']
            def initialize(self, start=0, stop=None, step=1):
                """
                Table-functions declare an initialize() method, which is
                called with whatever arguments the user has called the
                function with.
                """
                self.start = self.current = start
                self.stop = stop or float('Inf')
                self.step = step
            def iterate(self, idx):
                """
                Iterate is called repeatedly by the SQLite database engine
                until the required number of rows has been read **or** the
                function raises a `StopIteration` signalling no more rows
                are available.
                """
                if self.current > self.stop:
                    raise StopIteration
                ret, self.current = self.current, self.current + self.step
                return (ret,)
        cursor = db.execute_sql('SELECT * FROM series(?, ?, ?)', (0, 5, 2))
        for (value,) in cursor:
            print(value)
        # Prints:
        # 0
        # 2
        # 4

register_table_function(klass, name)

Parameters

•   klass (TableFunction) -- class implementing TableFunction API.

•   name (str) -- name for user-defined table function.

    Register a cysqlite.TableFunction class with the connection. Table functions are user-defined functions that, rather than returning a single, scalar value, can return any number of rows of tabular data.

    See also:

•   CySqliteDatabase.table_function() for example implementation.

•   cysqlite docs <https://cysqlite.readthedocs.io/en/latest/api.html#tablefunction> for details on TableFunction API.

unregister_table_function(name)

Parameters
    name -- Name of the user-defined table function.

Returns
    True or False, depending on whether the function was removed.

    Unregister the user-defined table function.

on_commit(fn)

Parameters
    fn -- callable or None to clear the current hook.

    Register a callback to be executed whenever a transaction is committed on the current connection. The callback accepts no parameters and the return value is ignored.

    However, if the callback raises a ValueError, the transaction will be aborted and rolled-back.

    Example:

        db = CySqliteDatabase(':memory:')
        @db.on_commit
        def on_commit():
            logger.info('COMMITing changes')

on_rollback(fn)

Parameters
    fn -- callable or None to clear the current hook.

    Register a callback to be executed whenever a transaction is rolled back on the current connection. The callback accepts no parameters and the return value is ignored.

    Example:

        @db.on_rollback
        def on_rollback():
            logger.info('Rolling back changes')

on_update(fn)

Parameters
    fn -- callable or None to clear the current hook.

    Register a callback to be executed whenever the database is written to (via an UPDATE, INSERT or DELETE query). The callback should accept the following parameters:

•   query - the type of query, either INSERT, UPDATE or DELETE.

•   database name - the default database is named main.

•   table name - name of table being modified.

•   rowid - the rowid of the row being modified.

    The callback's return value is ignored.

    Example:

        db = CySqliteDatabase(':memory:')
        @db.on_update
        def on_update(query_type, db, table, rowid):
            # e.g. INSERT row 3 into table users.
            logger.info('%s row %s into table %s', query_type, rowid, table)

authorizer(fn)

Parameters
    fn -- callable or None to clear the current authorizer.

    Register an authorizer callback. Authorizer callbacks must accept 5 parameters, which vary depending on the operation being checked.

•   op: operation code, e.g. cysqlite.SQLITE_INSERT.

•   p1: operation-specific value, e.g. table name for SQLITE_INSERT.

•   p2: operation-specific value.

•   p3: database name, e.g. "main".

•   p4: inner-most trigger or view responsible for the access attempt if applicable, else None.

    See sqlite authorizer documentation <https://www.sqlite.org/c3ref/c_alter_table.html> for description of authorizer codes and values for parameters p1 and p2.

    The authorizer callback must return one of:

•   cysqlite.SQLITE_OK: allow operation.

•   cysqlite.SQLITE_IGNORE: allow statement compilation but prevent the operation from occuring.

•   cysqlite.SQLITE_DENY: prevent statement compilation.

    More details can be found in the cysqlite docs <https://cysqlite.readthedocs.io/en/latest/api.html#Connection.authorizer>.

trace(fn, mask=2):

Parameters

•   fn -- callable or None to clear the current trace hook.

•   mask (int) -- mask of what types of events to trace. Default value corresponds to SQLITE_TRACE_PROFILE.

    Register a trace hook (sqlite3_trace_v2). Trace callback must accept 4 parameters, which vary depending on the operation being traced.

•   event: type of event, e.g. SQLITE_TRACE_PROFILE.

•   sid: memory address of statement (only SQLITE_TRACE_CLOSE), else -1.

•   sql: SQL string (only SQLITE_TRACE_STMT), else None.

•   ns: estimated number of nanoseconds the statement took to run (only SQLITE_TRACE_PROFILE), else -1.

    Any return value from callback is ignored.

    More details can be found in the cysqlite docs <https://cysqlite.readthedocs.io/en/latest/api.html#Connection.trace>.

progress(fn, n=1)

Parameters

•   fn -- callable or None to clear the current progress handler.

•   n (int) -- approximate number of VM instructions to execute between calls to the progress handler.

    Register a progress handler (sqlite3_progress_handler). Callback takes no arguments and returns 0 to allow progress to continue or any non-zero value to interrupt progress.

    More details can be found in the cysqlite docs <https://cysqlite.readthedocs.io/en/latest/api.html#Connection.progress>.

autocommit
    Property which returns a boolean indicating if autocommit is enabled. By default, this value will be True except when inside a transaction (or atomic() <#Database.atomic> block).

    Example:

        >>> db = CySqliteDatabase(':memory:')
        >>> db.autocommit
        True
        >>> with db.atomic():
        ...     print(db.autocommit)
        ...
        False
        >>> db.autocommit
        True

backup(destination, pages=None, name=None, progress=None)

Parameters

•   destination (CySqliteDatabase <#playhouse.cysqlite_ext.CySqliteDatabase>) -- Database object to serve as destination for the backup.

•   pages (int) -- Number of pages per iteration. Default value of -1 indicates all pages should be backed-up in a single step.

•   name (str) -- Name of source database (may differ if you used ATTACH DATABASE to load multiple databases). Defaults to "main".

•   progress -- Progress callback, called with three parameters: the number of pages remaining, the total page count, and whether the backup is complete.

    Example:

        master = CySqliteDatabase('master.db')
        replica = CySqliteDatabase('replica.db')
        # Backup the contents of master to replica.
        master.backup(replica)

backup_to_file(filename, pages, name, progress)

Parameters

•   filename -- Filename to store the database backup.

•   pages (int) -- Number of pages per iteration. Default value of -1 indicates all pages should be backed-up in a single step.

•   name (str) -- Name of source database (may differ if you used ATTACH DATABASE to load multiple databases). Defaults to "main".

•   progress -- Progress callback, called with three parameters: the number of pages remaining, the total page count, and whether the backup is complete.

    Backup the current database to a file. The backed-up data is not a database dump, but an actual SQLite database file.

    Example:

        db = CySqliteDatabase('app.db')
        def nightly_backup():
            filename = 'backup-%s.db' % (datetime.date.today())
            db.backup_to_file(filename)

blob_open(table, column, rowid, read_only=False)

Parameters

•   table (str) -- Name of table containing data.

•   column (str) -- Name of column containing data.

•   rowid (int) -- ID of row to retrieve.

•   read_only (bool) -- Open the blob for reading only.

•   dbname (str) -- Database name (e.g. if multiple databases attached).

Returns
    cysqlite.Blob instance which provides efficient access to the underlying binary data.

Return type
    cysqlite.Blob

    See cysqlite documentation <https://cysqlite.readthedocs.io/en/latest/api.html#blob> for more details.

    Example:

        class Image(Model):
            filename = TextField()
            data = BlobField()
        buf_size = 1024 * 1024 * 8  # Allocate 8MB for storing file.
        rowid = Image.insert({
            Image.filename: 'thefile.jpg',
            Image.data: fn.zeroblob(buf_size),
        }).execute()
        # Open the blob, returning a file-like object.
        blob = db.blob_open('image', 'data', rowid)
        # Write some data to the blob.
        blob.write(image_data)
        img_size = blob.tell()
        # Read the data back out of the blob.
        blob.seek(0)
        image_data = blob.read(img_size)

class PooledCySqliteDatabase(database, **kwargs)
    Connection-pooling variant of CySqliteDatabase.

  APSW

APSW <https://rogerbinns.github.io/apsw/> is a thin C wrapper over SQLite's C API that exposes nearly every SQLite feature including virtual tables, virtual filesystems, and BLOB I/O.

Installation:

    pip install apsw

Usage:

    from playhouse.apsw_ext import APSWDatabase
    db = APSWDatabase('my_app.db')
    class BaseModel(Model):
        class Meta:
            database = db

Warning: Use the Field subclasses from playhouse.apsw_ext rather than those from peewee to ensure correct type adaptation. For example, use playhouse.apsw_ext.DateTimeField instead of peewee.DateTimeField.

class APSWDatabase(database, **connect_kwargs)
    Subclass of SqliteDatabase <#SqliteDatabase> using the APSW driver.

Parameters

•   database (string) -- filename of sqlite database

•   connect_kwargs -- keyword arguments passed to apsw when opening a connection

register_module(mod_name, mod_inst)
    Register a virtual table module globally. See the APSW virtual table documentation <https://rogerbinns.github.io/apsw/vtable.html>.

Parameters

•   mod_name (string) -- name to use for module

•   mod_inst (object) -- an object implementing the Virtual Table <http://rogerbinns.github.io/apsw/vtable.html#vttable-class> interface

unregister_module(mod_name)
    Unregister a previously registered module.

  SQLCipher

SQLCipher <https://www.zetetic.net/sqlcipher/> is an encrypted wrapper around SQLite. Peewee exposes it through SqlCipherDatabase, which is API-identical to SqliteDatabase <#SqliteDatabase> except for its constructor.

Installation:

    pip install sqlcipher3

Usage:

    from playhouse.sqlcipher_ext import SqlCipherDatabase
    db = SqlCipherDatabase(
        'app.db',
        passphrase=os.environ['PASSPHRASE'],
        pragmas={'cache_size': -64000})

Example usage with deferred initialization and passphrase prompt:

    db = SqlCipherDatabase(None)
    class BaseModel(Model):
        class Meta:
            database = db
    class Secret(BaseModel):
        value = TextField()
    # Prompt the user and initialize the database with their passphrase.
    while True:
        db.init('my_app.db', passphrase=input('Passphrase: '))
        try:
            db.get_tables()  # Will raise if passphrase is wrong.
            break
        except DatabaseError as exc:
            print('Wrong passphrase.')
            db.init(None)

Pragma configuration (e.g. increasing PBKDF2 iterations):

    db = SqlCipherDatabase('my_app.db',
                           passphrase='s3cr3t',
                           pragmas={'kdf_iter': 1_000_000})

Note: SQLCipher can be configured using a number of extension PRAGMAs. The list of PRAGMAs and their descriptions can be found in the SQLCipher documentation <https://www.zetetic.net/sqlcipher/sqlcipher-api/>.

class SqlCipherDatabase(database, passphrase, **kwargs)

Parameters

•   database (str) -- Path to the encrypted database file.

•   passphrase (str) -- Encryption passphrase (should be 8 character minimum; enforce stronger requirements in your application).

    If the database file does not exist, it is created and encrypted with a key derived from passphrase. If it does exist, passphrase must match the one used when the file was created.

    If the passphrase is incorrect, an error will be raised when first attempting to access the database (typically DatabaseError: file is not a database).

rekey(passphrase)
    Change the encryption passphrase for the open database.

  SqliteQueueDatabase

SqliteQueueDatabase serializes all write queries through a single long-lived connection on a dedicated background thread. This allows multiple application threads to write to a SQLite database concurrently without conflict or timeout errors.

SqliteQueueDatabase can be used as a drop-in replacement for the regular SqliteDatabase <#SqliteDatabase> if you want simple read and write access to a SQLite database from multiple threads, and do not need transactions.

    from playhouse.sqliteq import SqliteQueueDatabase
    db = SqliteQueueDatabase(
        'my_app.db',
        use_gevent=False,    # Use stdlib threading (default).
        autostart=True,      # Start the writer thread immediately.
        queue_max_size=64,   # Max pending writes before blocking.
        results_timeout=5.0, # Seconds to wait for a write to complete.
        pragmas={'journal_mode': 'wal'})

If you set autostart=False, start the writer thread explicitly:

    db.start()

Stop the writer thread on application shutdown (waits for pending writes):

    import atexit
    @atexit.register
    def _stop():
        db.stop()

Read queries work as normal. Open and close the connection per-request as you would with any other database. Only writes are funneled through the queue.

Warning: Transactions are not supported. Because writes from different threads are interleaved, there is no way to guarantee that the statements in a transaction from one thread execute atomically without statements from another thread appearing between them. The atomic() and transaction() methods raise a ValueError if called.

If you need to temporarily bypass the queue and write directly (for example, during a batch import), use pause() and unpause().

class SqliteQueueDatabase(database, use_gevent=False, autostart=True, queue_max_size=None, results_timeout=None, **kwargs)

Parameters

•   database (str) -- database filename.

•   use_gevent (bool) -- use gevent instead of threading.

•   autostart (bool) -- automatically start writer background thread.

•   queue_max_size (int) -- maximum size of pending writes queue.

•   results_timeout (float) -- timeout for waiting for query results from write thread (seconds).

start()
    Start the background writer thread.

stop()
    Signal the writer thread to stop. Blocks until all pending writes are flushed.

is_stopped()
    Return True if the writer thread is not running.

pause()
    Block until the writer thread finishes its current work, then disconnect it. The calling thread takes over direct database access. Must be followed by a call to unpause().

unpause()
    Resume the writer thread and reconnect the queue.

  SQLite-Specific Fields

These field classes live in playhouse.sqlite_ext and can be used with:

•   SqliteDatabase <#SqliteDatabase>

•   CySqliteDatabase

•   APSWDatabase

•   SqlCipherDatabase

•   SqliteQueueDatabase

class RowIDField
    Primary-key field mapped to SQLite's implicit rowid column.

    For more information, see the SQLite documentation on rowid tables <https://www.sqlite.org/rowidtable.html>.

        class Note(Model):
            rowid = RowIDField()  # Implied primary_key=True.
            content = TextField()
            timestamp = TimestampField()

    RowIDField can be mapped to a different field name, but it's underlying column name will always be rowid.

        class Note(Model):
            id = RowIDField()
            ...

class AutoIncrementField
    Integer primary key that uses SQLite's AUTOINCREMENT keyword, guaranteeing the primary key is always strictly increasing even after deletions. Has a small performance cost versus the default PrimaryKeyField or RowIDField.

    See the SQLite AUTOINCREMENT documentation <https://sqlite.org/autoinc.html> for details.

class ISODateTimeField
    Subclass of DateTimeField <#DateTimeField> that preserves UTC offset information for timezone-aware datetimes when storing to SQLite's text-based datetime representation.

class TDecimalField(max_digits=10, decimal_places=5, auto_round=False, rounding=None, *args, **kwargs)
    Subclass of DecimalField <#DecimalField> that stores decimal values in a TEXT column to avoid any potential loss of precision that may occur when storing in a REAL (double-precision floating point) column. SQLite does not have a true numeric type, so this field ensures no precision is lost when using Decimals.

  SQLite JSON

JSONField enables storing and querying JSON data in SQLite using the SQLite json functions <https://sqlite.org/json1.html>.

class JSONField(json_dumps=None, json_loads=None, **kwargs)

Parameters

•   json_dumps -- Custom JSON serializer. Defaults to json.dumps.

•   json_loads -- Custom JSON deserializer. Defaults to json.loads.

    Stores and retrieves JSON data transparently and provides efficient implementations for in-place modification and querying. Data is automatically serialized on write, deserialized on read.

    Example model:

        from peewee import *
        from playhouse.sqlite_ext import JSONField
        db = SqliteDatabase(':memory:')
        class Config(db.Model):
            data = JSONField()
        Config.create_table()
        # Create two rows.
        Config.create(data={'timeout': 30, 'retry': {'max': 5}})
        Config.create(data={'timeout': 10, 'retry': {'max': 10}})

    To access or modify specific object keys or array indexes in a JSON structure, you can treat the JSONField as if it were a dictionary/list:

        # Select or order by a JSON value:
        query = (Config
                 .select(Config, Config.data['timeout'].alias('timeout'))
                 .order_by(Config.data['timeout'].desc()))
        # Aggregate on nested value:
        avg = (Config
               .select(fn.SUM(Config.data['timeout']) / fn.COUNT(Config.id))
               .scalar())
        # Filter by nested value:
        Config.select().where(Config.data['retry']['max'] < 8)

    Data can be atomically updated, written and removed in-place:

        # In-place update (preserves other keys):
        (Config
         .update(data=Config.data.update({'timeout': 60}))
         .where(Config.data['timeout'] >= 30)
         .execute())
        # Set a specific path:
        (Config
         .update(data=Config.data['timeout'].set(120))
         .where(Config.data['retry']['max'] == 5)
         .execute())
        # Update a specific path with an object. Existing field ("max") will be
        # preserved in this example.
        (Config
         .update(data=Config.data['retry'].update({'backoff': 1}))
         .execute())
        # To overwrite a specific path with an object, use set():
        (Config
         .update(data=Config.data['retry'].set({'allowed': 10}))
         .execute())
        # Remove a key atomically:
        (Config
         .update(data=Config.data.update({'retry': None}))
         .where(Config.id == 1)
         .execute())
        # Another way to remove atomically:
        (Config
         .update(data=Config.data['retry'].remove())
         .where(Config.id == 2)
         .execute())

    Helpers for other JSON scenarios:

        # Query JSON types:
        query = (Config
                 .select(Config.data.json_type(), Config.data['timeout'].json_type())
                 .tuples())
        # [('object', 'integer'), ('object', 'integer')]
        # Query length of an array:
        cfg1 = Config.create(data={'statuses': [1, 99, 1, 1]})
        cfg2 = Config.create(data={'statuses': [1, 1]})
        query = (Config
                 .select(
                     Config.data['statuses'],
                     Config.data['statuses'].length())
                 .tuples())
        # [([1, 99, 1, 1], 4), ([1, 1], 2)]

    Let's add a nested value and then see how to iterate through it's contents recursively using the tree() method:

        Config.create(data={'x1': {'y1': 'z1', 'y2': 'z2'}, 'x2': [1, 2]})
        tree = Config.data.tree().alias('tree')
        query = (Config
                 .select(Config.id, tree.c.fullkey, tree.c.value)
                 .from_(Config, tree))
        for row in query.tuples():
            print(row)
        (1, '$', {'x1': {'y1': 'z1', 'y2': 'z2'}, 'x2': [1, 2]}),
        (1, '$.x2', [1, 2]),
        (1, '$.x2[0]', 1),
        (1, '$.x2[1]', 2),
        (1, '$.x1', {'y1': 'z1', 'y2': 'z2'}),
        (1, '$.x1.y1', 'z1'),
        (1, '$.x1.y2', 'z2')]

    The tree() and children() methods are powerful. For more information on how to utilize them, see the json1 extension documentation <http://sqlite.org/json1.html#jtree>.

__getitem__(item)

Parameters
    item -- Access a specific key or array index in the JSON data.

Returns
    a special object exposing access to the JSON data.

Return type
    JSONPath <#playhouse.sqlite_ext.JSONPath>

    Access a specific key or array index in the JSON data. Returns a JSONPath object, which exposes convenient methods for reading or modifying a particular part of a JSON object.

    Example:

        # If metadata contains {"tags": ["list", "of", "tags"]}, we can
        # extract the first tag in this way:
        Post.select(Post, Post.metadata['tags'][0].alias('first_tag'))

    For more examples see the JSONPath API documentation.

extract(*paths)

Parameters
    paths -- One or more JSON paths to extract.

    Extract one or more JSON path values. Returns a list when multiple paths are given.

extract_json(path)

Parameters
    path (str) -- JSON path

    Extract the value at the specified path as a JSON data-type. This corresponds to the -> operator added in Sqlite 3.38.

extract_text(path)

Parameters
    path (str) -- JSON path

    Extract the value at the specified path as a SQL data-type. This corresponds to the ->> operator added in Sqlite 3.38.

set(value, as_json=None)

Parameters

•   value -- a scalar value, list, or dictionary.

•   as_json (bool) -- force the value to be treated as JSON, in which case it will be serialized as JSON in Python beforehand. By default, lists and dictionaries are treated as JSON to be serialized, while strings and integers are passed as-is.

    Set the value stored in a JSONField.

    Uses the json_set() <http://sqlite.org/json1.html#jset> function from the json1 extension.

replace(value, as_json=None)

Parameters

•   value -- a scalar value, list, or dictionary.

•   as_json (bool) -- force the value to be treated as JSON, in which case it will be serialized as JSON in Python beforehand. By default, lists and dictionaries are treated as JSON to be serialized, while strings and integers are passed as-is.

    Replace the existing value stored in a JSONField. Will not create if does not exist.

    Uses the json_replace() <http://sqlite.org/json1.html#jset> function from the json1 extension.

insert(value, as_json=None)

Parameters

•   value -- a scalar value, list, or dictionary.

•   as_json (bool) -- force the value to be treated as JSON, in which case it will be serialized as JSON in Python beforehand. By default, lists and dictionaries are treated as JSON to be serialized, while strings and integers are passed as-is.

    Insert value into JSONField. Will not overwrite existing.

    Uses the json_insert() <http://sqlite.org/json1.html#jset> function from the json1 extension.

append(value, as_json=None)

Parameters

•   value -- a scalar value, list, or dictionary.

•   as_json (bool) -- force the value to be treated as JSON, in which case it will be serialized as JSON in Python beforehand. By default, lists and dictionaries are treated as JSON to be serialized, while strings and integers are passed as-is.

    Append to the array stored in a JSONField.

    Uses the json_set() <http://sqlite.org/json1.html#jset> function from the json1 extension.

update(data)

Parameters
    data -- a scalar value, list or dictionary to merge with the data currently stored in a JSONField. To remove a particular key, set that key to None in the updated data.

    Merge new data into the JSON value using the RFC-7396 MergePatch algorithm to apply a patch (data parameter) against the column data. MergePatch can add, modify, or delete elements of a JSON object, which means update() is a generalized replacement for both set() and remove(). MergePatch treats JSON array objects as atomic, so update() cannot append to an array, nor modify individual elements of an array.

    For more information as well as examples, see the SQLite json_patch() <http://sqlite.org/json1.html#jpatch> function documentation.

remove()
    Remove the data stored in the JSONField.

    Uses the json_remove <https://www.sqlite.org/json1.html#jrm> function from the json1 extension.

json_type()
    Return a string identifying the type of value stored in the column.

    The type returned will be one of:

•   object

•   array

•   integer

•   real

•   true

•   false

•   text

•   null <-- the string "null" means an actual NULL value

•   NULL <-- an actual NULL value means the path was not found

    Uses the json_type <https://www.sqlite.org/json1.html#jtype> function from the json1 extension.

length()
    Return the length of the array stored in the column.

    Uses the json_array_length <https://www.sqlite.org/json1.html#jarraylen> function from the json1 extension.

children()
    The children function corresponds to json_each, a table-valued function that walks the JSON value provided and returns the immediate children of the top-level array or object. If a path is specified, then that path is treated as the top-most element.

    The rows returned by calls to children() have the following attributes:

•   key: the key of the current element relative to its parent.

•   value: the value of the current element.

•   type: one of the data-types (see json_type()).

•   atom: the scalar value for primitive types, NULL for arrays and objects.

•   id: a unique ID referencing the current node in the tree.

•   parent: the ID of the containing node.

•   fullkey: the full path describing the current element.

•   path: the path to the container of the current row.

    Internally this method uses the json_each <https://www.sqlite.org/json1.html#jeach> (documentation link) function from the json1 extension.

    Example usage (compare to tree() method):

        class KeyData(Model):
            key = TextField()
            data = JSONField()
        KeyData.create(key='a', data={'k1': 'v1', 'x1': {'y1': 'z1'}})
        KeyData.create(key='b', data={'x1': {'y1': 'z1', 'y2': 'z2'}})
        # We will query the KeyData model for the key and all the
        # top-level keys and values in it's data field.
        kd = KeyData.data.children().alias('children')
        query = (KeyData
                 .select(kd.c.key, kd.c.value, kd.c.fullkey)
                 .from_(KeyData, kd)
                 .order_by(kd.c.key)
                 .tuples())
        print(query[:])
        # PRINTS:
        [('a', 'k1', 'v1',                    '$.k1'),
         ('a', 'x1', '{"y1":"z1"}',           '$.x1'),
         ('b', 'x1', '{"y1":"z1","y2":"z2"}', '$.x1')]

tree()
    The tree function corresponds to json_tree, a table-valued function that recursively walks the JSON value provided and returns information about the keys at each level. If a path is specified, then that path is treated as the top-most element.

    The rows returned by calls to tree() have the same attributes as rows returned by calls to children():

•   key: the key of the current element relative to its parent.

•   value: the value of the current element.

•   type: one of the data-types (see json_type()).

•   atom: the scalar value for primitive types, NULL for arrays and objects.

•   id: a unique ID referencing the current node in the tree.

•   parent: the ID of the containing node.

•   fullkey: the full path describing the current element.

•   path: the path to the container of the current row.

    Internally this method uses the json_tree <https://www.sqlite.org/json1.html#jtree> (documentation link) function from the json1 extension.

    Example usage:

        class KeyData(Model):
            key = TextField()
            data = JSONField()
        KeyData.create(key='a', data={'k1': 'v1', 'x1': {'y1': 'z1'}})
        KeyData.create(key='b', data={'x1': {'y1': 'z1', 'y2': 'z2'}})
        # We will query the KeyData model for the key and all the
        # keys and values in it's data field, recursively.
        kd = KeyData.data.tree().alias('tree')
        query = (KeyData
                 .select(kd.c.key, kd.c.value, kd.c.fullkey)
                 .from_(KeyData, kd)
                 .order_by(kd.c.key)
                 .tuples())
        print(query[:])
        # PRINTS:
        [('a',  None,  '{"k1":"v1","x1":{"y1":"z1"}}', '$'),
         ('b',  None,  '{"x1":{"y1":"z1","y2":"z2"}}', '$'),
         ('a',  'k1',  'v1',                           '$.k1'),
         ('a',  'x1',  '{"y1":"z1"}',                  '$.x1'),
         ('b',  'x1',  '{"y1":"z1","y2":"z2"}',        '$.x1'),
         ('a',  'y1',  'z1',                           '$.x1.y1'),
         ('b',  'y1',  'z1',                           '$.x1.y1'),
         ('b',  'y2',  'z2',                           '$.x1.y2')]

class JSONPath(field, path=None)

Parameters

•   field (JSONField <#playhouse.sqlite_ext.JSONField>) -- the field object we intend to access.

•   path (tuple) -- Components comprising the JSON path.

    A convenient, Pythonic way of representing JSON paths for use with JSONField. Implements the same methods as JSONField but designed for operating on nested items, e.g.:

        Config.create(data={'timeout': 30, 'retries': {'max': 5}})
        # Both Config.data['timeout'] and Config.data['retries']['max']
        # are instances of JSONPath:
        query = (Config
                 .select(Config.data['timeout'])
                 .where(Config.data['retries']['max'] < 10))

class JSONBField(json_dumps=None, json_loads=None, **kwargs)
    Extends JSONField and stores data in the binary jsonb format (SQLite 3.45.0+). When reading raw column values the data is in its encoded binary form use the json() method to decode:

        # Raw read returns binary:
        kv = KV.get(KV.key == 'a')
        kv.value   # b"l'k1'v1"
        # Use .json() to get a Python object:
        kv = KV.select(KV.value.json()).get()
        kv.value   # {'k1': 'v1'}

json()
    Indicate the JSONB field-data should be deserialized and returned as JSON (as opposed to the SQLite binary format).

  Full-Text Search

Peewee supports FTS3, FTS4 (legacy, widely available) and FTS5 full-text search extensions.

The general pattern is:

1.  Define a FTSModel or FTS5Model subclass with one or more SearchField columns.

2.  When a row is created or updated in the source table, insert or update the corresponding row in the search index.

3.  Query the index using match() and rank results with bm25() (or rank() for FTS5).

    Consult the SQLite documentation for FTS query syntax diagrams:

•   FTS3 and FTS4 <https://www.sqlite.org/fts3.html#full_text_index_queries>

•   FTS5 <https://sqlite.org/fts5.html#full_text_query_syntax>

class SearchField(unindexed=False, column_name=None)
    Field type for full-text search virtual tables. Raises an exception if constraints (null=False, unique=True, etc.) are specified, since FTS tables do not support them.

    Pass unindexed=True to store metadata alongside the search index without indexing it:

        class DocumentIndex(FTSModel):
            title = SearchField()
            content = SearchField()
            tags = SearchField()
            timestamp = SearchField(unindexed=True)

match(term)

Parameters
    term (str) -- full-text search query/terms.

Returns
    a Expression <#Expression> corresponding to the MATCH operator.

    Sqlite's full-text search supports searching either the full table, including all indexed columns, or searching individual columns. The match() method can be used to restrict search to a single column:

        # Search *only* the title field and return results ordered by
        # relevance, using bm25.
        query = (DocumentIndex
                 .select(DocumentIndex, DocumentIndex.bm25().alias('score'))
                 .where(DocumentIndex.title.match('python'))
                 .order_by(DocumentIndex.bm25()))

    To search all indexed columns, use the FTSModel.match() method:

        # Searches *both* the title and body and return results ordered by
        # relevance, using bm25.
        query = (DocumentIndex
                 .select(DocumentIndex, DocumentIndex.bm25().alias('score'))
                 .where(DocumentIndex.match('python'))
                 .order_by(DocumentIndex.bm25()))

highlight(left, right)

Parameters

•   left (str) -- opening tag for highlight, e.g. '<b>'

•   right (str) -- closing tag for highlight, e.g. '</b>'

    When performing a search using the MATCH operator, FTS5 can return text highlighting matches in a given column.

        # Search for items matching string 'python' and return the title
        # highlighted with square brackets.
        query = (SearchIndex
                 .search('python')
                 .select(SearchIndex.title.highlight('[', ']').alias('hi')))
        for result in query:
            print(result.hi)
        # For example, might print:
        # Learn [python] the hard way

snippet(left, right, over_length='...', max_tokens=16)

Parameters

•   left (str) -- opening tag for highlight, e.g. '<b>'

•   right (str) -- closing tag for highlight, e.g. '</b>'

•   over_length (str) -- text to prepend or append when snippet exceeds the maximum number of tokens.

•   max_tokens (int) -- max tokens returned, must be 1 - 64.

    When performing a search using the MATCH operator, FTS5 can return text with a snippet containing the highlighted match in a given column.

        # Search for items matching string 'python' and return the title
        # highlighted with square brackets.
        query = (SearchIndex
                 .search('python')
                 .select(SearchIndex.title.snippet('[', ']').alias('snip')))
        for result in query:
            print(result.snip)

  FTS4 / FTSModel

FTSModel enables Peewee applications to store data in an efficient full-text search index using SQLite FTS4 <https://www.sqlite.org/fts3.html>.

FTSModel caveats:

•   All queries except MATCH and rowid lookup require a full table scan.

•   Constraints, foreign-keys, and indexes are not supported.

•   All columns are treated as TEXT.

•   No built-in ranking. Peewee provides several implementations which can be automatically registered by passing rank_functions=True to SqliteDatabase(...).

•   FTSModel rowid primary key may be declared using RowIDField. Lookups on the rowid are very efficient.

    Given these constraints all fields besides rowid should be instances of SearchField to ensure correctness.

    Tip: Because of the lack of secondary indexes, it usually makes sense to treat the FTSModel.rowid primary key as a foreign-key to a row in a normal SQLite table.

    Example:

        from peewee import *
        from playhouse.sqlite_ext import FTSModel, SearchField
        db = SqliteDatabase('app.db', rank_functions=True)
        class Document(Model):
            # Canonical source of data, stored in a normal table.
            author = ForeignKeyField(User, backref='documents')
            title = TextField(null=False, unique=True)
            content = TextField(null=False)
            timestamp = DateTimeField()
            class Meta:
                database = db
        class DocumentIndex(FTSModel):
            # Full-text search index.
            rowid = RowIDField()
            title = SearchField()
            content = SearchField()
            author = SearchField(unindexed=True)
            class Meta:
                database = db
                # Use the porter stemming algorithm to tokenize content, optimize
                # prefix searches of 3 or 4 characters.
                options = {'tokenize': 'porter unicode61', 'prefix': [3, 4]}

    Store data by inserting it into the FTS table:

        # Store a document in the index:
        DocumentIndex.create(
            rowid=document.id,  # Set rowid to match Document's id.
            title=document.title,
            content=document.content,
            author=document.author.get_full_name())
        # Equivalent:
        (DocumentIndex
         .insert({
             'rowid': document.id,
             'title': document.title,
             'content': document.content,
             'author': document.author.get_full_name()})
         .execute())

    FTSModel provides several shortcuts for full-text search queries:

        # Simple search using basic ranking algorithm.
        results = DocumentIndex.search('python sqlite')
        # BM25 search With score and per-column weighting:
        results = DocumentIndex.search_bm25(
            'python sqlite',
            weights={'title': 2.0, 'content': 1.0},
            with_score=True,
            score_alias='relevance')
        for r in results:
            print(r.title, r.relevance)

    Tip: An important method of searching relies on the rowid of the indexed data matching the document's canonical id. Using this technique we can apply additional filters and retrieve the matching Document objects efficiently:

        # Search and ensure we only retrieve articles from the last 30 days.
        cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
        query = (Document
                 .select()
                 .join(
                     DocumentIndex,
                     on=(Document.id == DocumentIndex.rowid))
                 .where(
                     (Document.timestamp >= cutoff) &
                     DocumentIndex.match('python sqlite'))
                 .order_by(DocumentIndex.bm25()))

    Warning: All SQL queries on FTSModel classes will be full-table scans except full-text searches and rowid lookups.

  External Content

If the primary source of the content you are indexing exists in a separate table, you can save some disk space by instructing SQLite to not store an additional copy of the search index content.

To accomplish this, you can specify a table using the content option. The FTS4 documentation <https://www.sqlite.org/fts3.html#the_content_option_> and FTS5 documentation <https://www.sqlite.org/fts5.html#external_content_and_contentless_tables> have more information.

Here is a short example illustrating how to implement this with peewee:

    class Blog(Model):
        title = TextField()
        pub_date = DateTimeField(default=datetime.datetime.now)
        content = TextField()  # We want to search this.
        class Meta:
            database = db
    class BlogIndex(FTSModel):  # or FTS5Model.
        content = SearchField()
        class Meta:
            database = db
            options = {
                'content': Blog,  # Data source.
                'content_rowid': Blog.id,  # FTS5 only.
            }
    db.create_tables([Blog, BlogIndex])
    # Now, we can manage content in the BlogIndex. To populate the
    # search index:
    BlogIndex.rebuild()
    # Optimize the index.
    BlogIndex.optimize()

The content option accepts a Model <#Model> and can reduce the amount of storage used by the database at the expense of requiring more care and attention to keeping data synchronized.

class FTSModel
    Base Model class suitable for working with SQLite FTS3 / FTS4.

    Supports the following options:

•   content: Model <#Model> containing external content, or empty string for "contentless"

•   prefix: integer(s). Ex: '2' or '2,3,4'

•   tokenize: simple, porter, unicode61. Ex: 'porter'

    Example:

        class DocumentIndex(FTSModel):
            title = SearchField()
            body = SearchField()
            class Meta:
                database = db
                options = {
                    'tokenize': 'porter unicode61',
                    'prefix': '3',
                }

classmethod match(term)

Parameters
    term -- Search term or expression. FTS syntax documentation <https://www.sqlite.org/fts3.html#full_text_index_queries>.

    Generate a SQL expression representing a search for the given term or expression in the table. SQLite uses the MATCH operator to indicate a full-text search.

    Example:

        # Search index for "search phrase" and return results ranked
        # by relevancy using the BM25 algorithm.
        query = (DocumentIndex
                 .select()
                 .where(DocumentIndex.match('search phrase'))
                 .order_by(DocumentIndex.bm25()))
        for result in query:
            print('Result: %s' % result.title)

classmethod search(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)

Parameters

•   term -- Search term or expression. FTS syntax documentation <https://www.sqlite.org/fts3.html#full_text_index_queries>.

•   weights -- A list of weights for the columns, ordered with respect to the column's position in the table. Or, a dictionary keyed by the field or field name and mapped to a value.

•   with_score -- Whether the score should be returned as part of the SELECT statement.

•   score_alias (str) -- Alias to use for the calculated rank score. This is the attribute you will use to access the score if with_score=True.

•   explicit_ordering (bool) -- Order using full SQL function to calculate rank, as opposed to simply referencing the score alias in the ORDER BY clause.

    Shorthand way of searching for a term and sorting results by the quality of the match.

    Note: This method uses a simplified algorithm for determining the relevance rank of results. For more sophisticated result ranking, use the search_bm25() method.

        # Simple search.
        docs = DocumentIndex.search('search term')
        for result in docs:
            print(result.title)
        # More complete example.
        docs = DocumentIndex.search(
            'search term',
            weights={'title': 2.0, 'content': 1.0},
            with_score=True,
            score_alias='search_score')
        for result in docs:
            print(result.title, result.search_score)

classmethod search_bm25(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)

Parameters

•   term -- Search term or expression. FTS syntax documentation <https://www.sqlite.org/fts3.html#full_text_index_queries>.

•   weights -- A list of weights for the columns, ordered with respect to the column's position in the table. Or, a dictionary keyed by the field or field name and mapped to a value.

•   with_score -- Whether the score should be returned as part of the SELECT statement.

•   score_alias (str) -- Alias to use for the calculated rank score. This is the attribute you will use to access the score if with_score=True.

•   explicit_ordering (bool) -- Order using full SQL function to calculate rank, as opposed to simply referencing the score alias in the ORDER BY clause.

    Shorthand way of searching for a term and sorting results by the quality of the match using the BM25 algorithm.

    Attention: The BM25 ranking algorithm is only available for FTS4. If you are using FTS3, use the search() method instead.

classmethod search_bm25f(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)
    Same as FTSModel.search_bm25(), but using the BM25f variant of the BM25 ranking algorithm.

classmethod search_lucene(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)
    Same as FTSModel.search_bm25(), but using the result ranking algorithm from the Lucene search engine.

classmethod rank(col1_weight, col2_weight...coln_weight)

Parameters
    col_weight (float) -- (Optional) weight to give to the ith column of the model. By default all columns have a weight of 1.0.

    Generate an expression that will calculate and return the quality of the search match. This rank can be used to sort the search results.

    The rank function accepts optional parameters that allow you to specify weights for the various columns. If no weights are specified, all columns are considered of equal importance.

    Note: The algorithm used by rank() is simple and relatively quick. For more sophisticated result ranking, use:

•   bm25()

•   bm25f()

•   lucene()

        query = (DocumentIndex
                 .select(
                     DocumentIndex,
                     DocumentIndex.rank().alias('score'))
                 .where(DocumentIndex.match('search phrase'))
                 .order_by(DocumentIndex.rank()))
        for search_result in query:
            print(search_result.title, search_result.score)

classmethod bm25(col1_weight, col2_weight...coln_weight)

Parameters
    col_weight (float) -- (Optional) weight to give to the ith column of the model. By default all columns have a weight of 1.0.

    Generate an expression that will calculate and return the quality of the search match using the BM25 algorithm <https://en.wikipedia.org/wiki/Okapi_BM25>. This value can be used to sort the search results.

    Like rank(), bm25 function accepts optional parameters that allow you to specify weights for the various columns. If no weights are specified, all columns are considered of equal importance.

    Attention: The BM25 result ranking algorithm requires FTS4. If you are using FTS3, use rank() instead.

        query = (DocumentIndex
                 .select(
                     DocumentIndex,
                     DocumentIndex.bm25().alias('score'))
                 .where(DocumentIndex.match('search phrase'))
                 .order_by(DocumentIndex.bm25()))
        for search_result in query:
            print(search_result.title, search_result.score)

    Note: The above code example is equivalent to calling the search_bm25() method:

        query = DocumentIndex.search_bm25('search phrase', with_score=True)
        for search_result in query:
            print(search_result.title, search_result.score)

classmethod bm25f(col1_weight, col2_weight...coln_weight)
    Identical to bm25(), except that it uses the BM25f variant of the BM25 ranking algorithm.

classmethod lucene(col1_weight, col2_weight...coln_weight)
    Identical to bm25(), except that it uses the Lucene search result ranking algorithm.

classmethod rebuild()
    Rebuild the search index. Only valid when the content option was specified (content tables).

classmethod optimize()
    Optimize the index.

  FTS5 / FTS5Model

FTS5Model enables Peewee applications to store data in an efficient full-text search index using SQLite FTS5 <https://www.sqlite.org/fts5.html>. FTS5 also comes with native BM25 result ranking.

FTS5Model caveats:

•   All queries except MATCH and rowid lookup require a full table scan.

•   Constraints, foreign-keys, and indexes are not supported. All columns must be instances of SearchField.

•   FTS5Model rowid primary key may be declared using RowIDField. Lookups on the rowid are very efficient.

    Tip: Because of the lack of secondary indexes, it usually makes sense to treat the FTS5Model.rowid primary key as a foreign-key to a row in a normal SQLite table.

    Example:

        from peewee import *
        from playhouse.sqlite_ext import FTS5Model, SearchField
        db = SqliteDatabase('app.db')
        class Document(Model):
            # Canonical source of data, stored in a normal table.
            author = ForeignKeyField(User, backref='documents')
            title = TextField(null=False, unique=True)
            content = TextField(null=False)
            timestamp = DateTimeField()
            class Meta:
                database = db
        class DocumentIndex(FTS5Model):
            # Full-text search index.
            rowid = RowIDField()
            title = SearchField()
            content = SearchField()
            author = SearchField(unindexed=True)
            class Meta:
                database = db
                # Use the porter stemming algorithm and unicode tokenizers,
                # and optimize prefix matches of 3 or 4 characters.
                options = {'tokenize': 'porter unicode61', 'prefix': [3, 4]}
        # Check that FTS5 is available:
        if not DocumentIndex.fts5_installed():
            raise RuntimeError('FTS5 is not available in this SQLite build.')

    Store data by inserting it into the FTS5 table:

        # Store a document in the index:
        DocumentIndex.create(
            rowid=document.id,  # Set rowid to match Document's id.
            title=document.title,
            content=document.content,
            author=document.author.get_full_name())
        # Equivalent:
        (DocumentIndex
         .insert({
             'rowid': document.id,
             'title': document.title,
             'content': document.content,
             'author': document.author.get_full_name()})
         .execute())

    FTS5Model provides several shortcuts for full-text search queries:

        # Simple search (BM25, ordered by relevance):
        results = DocumentIndex.search('python sqlite')
        # With score and per-column weighting:
        results = DocumentIndex.search(
            'python sqlite',
            weights={'title': 2.0, 'content': 1.0},
            with_score=True,
            score_alias='relevance')
        for r in results:
            print(r.title, r.relevance)
        # Highlight matches in the title:
        for r in (DocumentIndex.search('python')
                  .select(DocumentIndex.title.highlight('[', ']').alias('hi'))):
            print(r.hi)  # e.g. "Learn [python] the hard way"

    Tip: An important method of searching relies on the rowid of the indexed data matching the document's canonical id. Using this technique we can apply additional filters and retrieve the matching Document objects efficiently:

        # Search and ensure we only retrieve articles from the last 30 days.
        cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
        query = (Document
                 .select()
                 .join(
                     DocumentIndex,
                     on=(Document.id == DocumentIndex.rowid))
                 .where(
                     (Document.timestamp >= cutoff) &
                     DocumentIndex.match('python sqlite'))
                 .order_by(DocumentIndex.rank()))

    If the primary source of the content you are indexing exists in a separate table, you can save some disk space by instructing SQLite to not store an additional copy of the search index content. See External Content for implementation details. The FTS5 documentation <https://www.sqlite.org/fts5.html#external_content_and_contentless_tables> has more information.

class FTS5Model
    Inherits all FTSModel methods plus.

    Supports the following options:

•   content: Model <#Model> containing external content, or empty string for "contentless"

•   content_rowid: Field <#Field> (external content primary key)

•   prefix: integer(s). Ex: '2' or [2, 3]

•   tokenize: simple, porter, unicode61. Ex: 'porter unicode61'

    Example:

        class DocumentIndex(FTS5Model):
            title = SearchField()
            body = SearchField()
            class Meta:
                database = db
                options = {
                    'tokenize': 'porter unicode61',
                    'prefix': '3',
                }

classmethod fts5_installed()
    Return True if FTS5 is available.

classmethod match(term)

Parameters
    term -- Search term or expression. FTS5 syntax documentation <https://sqlite.org/fts5.html#full_text_query_syntax>.

    Generate a SQL expression representing a search for the given term or expression in the table. SQLite uses the MATCH operator to indicate a full-text search.

    Example:

        # Search index for "search phrase" and return results ranked
        # by relevancy using the BM25 algorithm.
        query = (DocumentIndex
                 .select()
                 .where(DocumentIndex.match('search phrase'))
                 .order_by(DocumentIndex.rank()))
        for result in query:
            print('Result: %s' % result.title)

classmethod search(term, weights=None, with_score=False, score_alias='score')

Parameters

•   term -- Search term or expression. FTS5 syntax documentation <https://sqlite.org/fts5.html#full_text_query_syntax>.

•   weights -- A list of weights for the columns, ordered with respect to the column's position in the table. Or, a dictionary keyed by the field or field name and mapped to a value.

•   with_score -- Whether the score should be returned as part of the SELECT statement.

•   score_alias (str) -- Alias to use for the calculated rank score. This is the attribute you will use to access the score if with_score=True.

•   explicit_ordering (bool) -- Order using full SQL function to calculate rank, as opposed to simply referencing the score alias in the ORDER BY clause.

    Shorthand way of searching for a term and sorting results by the quality of the match. The FTS5 extension provides a built-in implementation of the BM25 algorithm, which is used to rank the results by relevance.

        # Simple search.
        docs = DocumentIndex.search('search term')
        for result in docs:
            print(result.title)
        # More complete example.
        docs = DocumentIndex.search(
            'search term',
            weights={'title': 2.0, 'content': 1.0},
            with_score=True,
            score_alias='search_score')
        for result in docs:
            print(result.title, result.search_score)

classmethod search_bm25(term, weights=None, with_score=False, score_alias='score')
    With FTS5, search_bm25() is identical to the search() method.

classmethod rank(col1_weight, col2_weight...coln_weight)

Parameters
    col_weight (float) -- (Optional) weight to give to the ith column of the model. By default all columns have a weight of 1.0.

    Generate an expression that will calculate and return the quality of the search match using the BM25 algorithm <https://en.wikipedia.org/wiki/Okapi_BM25>. This value can be used to sort the search results.

    The rank() function accepts optional parameters that allow you to specify weights for the various columns. If no weights are specified, all columns are considered of equal importance.

        query = (DocumentIndex
                 .select(
                     DocumentIndex,
                     DocumentIndex.rank().alias('score'))
                 .where(DocumentIndex.match('search phrase'))
                 .order_by(DocumentIndex.rank()))
        for search_result in query:
            print(search_result.title, search_result.score)

    Note: The above code example is equivalent to calling the search() method:

        query = DocumentIndex.search('search phrase', with_score=True)
        for search_result in query:
            print(search_result.title, search_result.score)

classmethod bm25(col1_weight, col2_weight...coln_weight)
    Because FTS5 provides built-in support for BM25, this method is identical to rank() method.

classmethod VocabModel(table_type='row' | 'col' | 'instance', table_name=None)

Parameters

•   table_type (str) -- Either 'row', 'col' or 'instance'.

•   table_name -- Name for the vocab table. If not specified, will be "fts5tablename_v".

    Generate a model class suitable for accessing the vocab table <http://sqlite.org/fts5.html#the_fts5vocab_virtual_table_module> corresponding to FTS5 search index.

classmethod rebuild()
    Rebuild the search index. Only valid when the content option was specified (content tables).

classmethod optimize()
    Optimize the index.

  User-Defined Function Collection

The playhouse.sqlite_udf contains a number of functions, aggregates, and table-valued functions grouped into named collections.

    from playhouse.sqlite_udf import register_all, register_groups
    from playhouse.sqlite_udf import DATE, STRING
    db = SqliteDatabase('my_app.db')
    register_all(db)                   # Register every function.
    register_groups(db, DATE, STRING)  # Register selected groups.
    # Register individual functions:
    from playhouse.sqlite_udf import gzip, gunzip
    db.register_function(gzip, 'gzip')
    db.register_function(gunzip, 'gunzip')

Once registered, call functions via Peewee's fn namespace or raw SQL:

    # Find most common URL hostnames.
    query = (Link
             .select(fn.hostname(Link.url).alias('host'), fn.COUNT(Link.id))
             .group_by(fn.hostname(Link.url))
             .order_by(fn.COUNT(Link.id).desc())
             .tuples())

  Available functions

CONTROL_FLOW

if_then_else(cond, truthy, falsey=None)
    Simple ternary-type operator, where, depending on the truthiness of the cond parameter, either the truthy or falsey value will be returned.

    DATE

strip_tz(date_str)

Parameters
    date_str -- A datetime, encoded as a string.

Returns
    The datetime with any timezone info stripped off.

    The time is not adjusted in any way, the timezone is simply removed.

humandelta(nseconds, glue=', ')

Parameters

•   nseconds (int) -- Number of seconds, total, in timedelta.

•   glue (str) -- Fragment to join values.

Returns
    Easy-to-read description of timedelta.

    Example, 86471 -> "1 day, 1 minute, 11 seconds"

mintdiff(datetime_value)

Parameters
    datetime_value -- A date-time.

Returns
    Minimum difference between any two values in list.

    Aggregate: minimum difference between any two datetimes.

avgtdiff(datetime_value)

Parameters
    datetime_value -- A date-time.

Returns
    Average difference between values in list.

    Aggregate: average difference between consecutive values.

duration(datetime_value)

Parameters
    datetime_value -- A date-time.

Returns
    Duration from smallest to largest value in list, in seconds.

    Aggregate: duration from the smallest to the largest value, in seconds.

date_series(start, stop, step_seconds=86400)

Parameters

•   start (datetime) -- Start datetime

•   stop (datetime) -- Stop datetime

•   step_seconds (int) -- Number of seconds comprising a step.

    Table-value function: returns rows consisting of the date/+time values encountered iterating from start to stop, step_seconds at a time.

    Additionally, if start does not have a time component and step_seconds is greater-than-or-equal-to one day (86400 seconds), the values returned will be dates. Conversely, if start does not have a date component, values will be returned as times. Otherwise values are returned as datetimes.

    Example:

        SELECT * FROM date_series('2017-01-28', '2017-02-02');
        value
        -----
        2017-01-28
        2017-01-29
        2017-01-30
        2017-01-31
        2017-02-01
        2017-02-02

    FILE

file_ext(filename)

Parameters
    filename (str) -- Filename to extract extension from.

Returns
    Returns the file extension, including the leading ".".

file_read(filename)

Parameters
    filename (str) -- Filename to read.

Returns
    Contents of the file.

    HELPER

gzip(data, compression=9)

Parameters

•   data (bytes) -- Data to compress.

•   compression (int) -- Compression level (9 is max).

Returns
    Compressed binary data.

gunzip(data)

Parameters
    data (bytes) -- Compressed data.

Returns
    Uncompressed binary data.

hostname(url)

Parameters
    url (str) -- URL to extract hostname from.

Returns
    hostname portion of URL

toggle(key)

Parameters
    key -- Key to toggle.

    Toggle a key between True/False state. Example:

        >>> toggle('my-key')
        True
        >>> toggle('my-key')
        False
        >>> toggle('my-key')
        True

setting(key, value=None)

Parameters

•   key -- Key to set/retrieve.

•   value -- Value to set.

Returns
    Value associated with key.

    Store/retrieve a setting in memory and persist during lifetime of application. To get the current value, specify key. To set a new value, call with key and new value.

clear_toggles()
    Clears all state associated with the toggle() function.

clear_settings()
    Clears all state associated with the setting() function.

    MATH

randomrange(start, stop=None, step=None)

Parameters

•   start (int) -- Start of range (inclusive)

•   end (int) -- End of range(not inclusive)

•   step (int) -- Interval at which to return a value.

    Return a random integer between [start, end).

gauss_distribution(mean, sigma)

Parameters

•   mean (float) -- Mean value

•   sigma (float) -- Standard deviation

sqrt(n)
    Calculate the square root of n.

tonumber(s)

Parameters
    s (str) -- String to convert to number.

Returns
    Integer, floating-point or NULL on failure.

mode(val)

Parameters
    val -- Numbers in list.

Returns
    The mode, or most-common, number observed.

    Aggregate: calculates mode of values.

minrange(val)

Parameters
    val -- Value

Returns
    Min difference between two values.

    Aggregate: minimum distance between two numbers in the sequence.

avgrange(val)

Parameters
    val -- Value

Returns
    Average difference between values.

    Aggregate: average distance between consecutive numbers in the sequence.

range(val)

Parameters
    val -- Value

Returns
    The range from the smallest to largest value in sequence.

    Aggregate: range of values observed.

median(val)

Parameters
    val -- Value

Returns
    The median, or middle, value in a sequence.

    Aggregate: median value of a sequence.

    Note: Only available if you compiled the _sqlite_udf extension.

    STRING

substr_count(haystack, needle)
    Returns number of times needle appears in haystack.

strip_chars(haystack, chars)
    Strips any characters in chars from beginning and end of haystack.

damerau_levenshtein_dist(s1, s2)
    Computes the edit distance from s1 to s2 using the damerau variant of the levenshtein algorithm.

    Note: Only available if you compiled the _sqlite_udf extension.

levenshtein_dist(s1, s2)
    Computes the edit distance from s1 to s2 using the levenshtein algorithm.

    Note: Only available if you compiled the _sqlite_udf extension.

str_dist(s1, s2)
    Computes the edit distance from s1 to s2 using the standard library SequenceMatcher's algorithm.

    Note: Only available if you compiled the _sqlite_udf extension.

regex_search(regex, search_string)

Parameters

•   regex (str) -- Regular expression

•   search_string (str) -- String to search for instances of regex.

    Table-value function: searches a string for substrings that match the provided regex. Returns rows for each match found.

    Example:

        SELECT * FROM regex_search('\w+', 'extract words, ignore! symbols');
        value
        -----
        extract
        words
        ignore
        symbols

  Postgresql

The playhouse.postgres_ext module exposes Postgresql-specific field types and features that are not available in the standard PostgresqlDatabase <#PostgresqlDatabase>.

  On this page

•   Getting Started

•   PostgresqlExtDatabase

•   JSON Support

•   HStore

•   Arrays

•   Interval

•   DateTimeTZ Field

•   Full-Text Search

•   Server-Side Cursors

•   CockroachDB

  Getting Started

To get started import the playhouse.postgres_ext module and use the PostgresqlExtDatabase database class:

    from playhouse.postgres_ext import *
    db = PostgresqlExtDatabase('peewee_test', user='postgres')
    class BaseExtModel(Model):
        class Meta:
            database = db

  PostgresqlExtDatabase

class PostgresqlExtDatabase(database, server_side_cursors=False, register_hstore=False, prefer_psycopg3=False, **kwargs)
    Extends PostgresqlDatabase <#PostgresqlDatabase> and is required to use:

•   ArrayField

•   DateTimeTZField

•   JSONField / BinaryJSONField

•   HStoreField

•   TSVectorField

•   Server-Side Cursors

Parameters

•   database (str) -- Name of database to connect to.

•   server_side_cursors (bool) -- Whether SELECT queries should utilize server-side cursors.

•   register_hstore (bool) -- Register the hstore extension.

•   prefer_psycopg3 (bool) -- If both psycopg2 and psycopg3 are installed, instruct Peewee to prefer psycopg3.

    When using server_side_cursors be sure to wrap your queries with ServerSide().

class PooledPostgresqlExtDatabase(database, **kwargs)
    Connection-pooling variant of PostgresqlExtDatabase.

class Psycopg3Database(database, **kwargs)
    Same as PostgresqlExtDatabase but specifies prefer_psycopg3=True.

class PooledPsycopg3Database(database, **kwargs)
    Connection-pooling variant of Psycopg3Database.

  JSON Support

Peewee provides two JSON field types for Postgresql:

•   JSONField - stores JSON as text, supports key access and comparison.

•   BinaryJSONField - stores JSON in the efficient binary jsonb format and adds containment operators.

    In general always use BinaryJSONField.

        from playhouse.postgres_ext import PostgresqlExtDatabase, BinaryJSONField
        db = PostgresqlExtDatabase('my_app')
        class Event(Model):
            data = BinaryJSONField()
            class Meta:
                database = db
        # Store data:
        Event.create(data={
            'type': 'login',
            'user_id': 42,
            'request': {'ip': '1.2.3.4'},
            'success': True})
        # Filter using a nested key:
        query = (Event
                 .select()
                 .where(Event.data['request']['ip'] == '1.2.3.4'))
        # Select, group and order-by JSON values.
        query = (Event
                 .select(Event.data['user_id'],
                         fn.COUNT(Event.id))
                 .group_by(Event.data['user_id'])
                 .order_by(Event.data['user_id'])
                 .tuples())
        # Retrieve JSON objects.
        query = (Event
                 .select(Event.data['request'].as_json().alias('request'))
                 .where(Event.data['user_id'] == 42))
        for event in query:
            print(event.request['ip'])

    Tip: Refer to the Postgresql JSON documentation <https://www.postgresql.org/docs/current/functions-json.html> for in-depth discussion and examples of using JSON and JSONB.

  JSONField and BinaryJSONField

class JSONField(dumps=None, *args, **kwargs)

Parameters
    dumps -- custom implementation of json.dumps

    Field that stores and retrieves JSON data. Supports __getitem__ key access for filtering and sub-object retrieval.

    Note: Consider using the BinaryJSONField instead as it offers better performance and more powerful querying options.

as_json()
    Deserialize and return the JSON value at the given path.

concat(data)
    Concatenate the field value with data. Note this is a shallow operation and does not deep-merge nested objects.

    Example:

        # Add object - if "result" key existed before it is overwritten.
        (Event
         .update(data=Event.data.concat({'result': {'success': True}}))
         .execute())

class BinaryJSONField(dumps=None, *args, **kwargs)

Parameters
    dumps -- custom implementation of json.dumps

    Extends JSONField for the jsonb type.

    Note: By default BinaryJSONField will use a GiST index. To disable this, initialize the field with index=False.

as_json()
    Deserialize and return the JSON value at the given path.

concat(data)
    Concatenate the field value with data. Note this is a shallow operation and does not deep-merge nested objects.

    Example:

        # Add object - if "result" key existed before it is overwritten.
        (Event
         .update(data=Event.data.concat({'result': {'success': True}}))
         .execute())

contains(other)
    Test whether this field's value contains other (as a subset). other may be a partial dict, list, or scalar value.

        Event.create(data={
            'type': 'rename',
            'name': 'new name',
            'tags': ['t1', 't2', 't3']})
        # Search by partial object:
        Event.select().where(Event.data.contains({'type': 'rename'}))
        # Search arrays by one or more items:
        Event.select().where(Event.data['tags'].contains(['t2', 't1']))
        # Search arrays by individual item:
        Event.select().where(Event.data['tags'].contains('t1'))

contains_any(*keys)
    Test whether any of keys is present in the JSON value.

        Event.create(data={
            'type': 'rename',
            'name': 'new name',
            'tags': ['t1', 't2', 't3']})
        (Event
         .select()
         .where(Event.data.contains_any('name', 'other')))

contains_all(*keys)
    Test whether all of keys are present in the JSON value.

        Event.create(data={
            'type': 'rename',
            'name': 'new name',
            'tags': ['t1', 't2', 't3']})
        (Event
         .select()
         .where(Event.data.contains_all('name', 'tags')))

contained_by(other)
    Test whether this field's value is a subset of other.

        Event.create(data={
            'type': 'login',
            'result': {'success': True}})
        (Event
         .select()
         .where(Event.data.contained_by({
             'type': 'login',
             'result': {'success': True, 'message': 'OK'}})))

has_key(key)
    Test whether key exists.

        Event.select().where(Event.data.has_key('result'))
        Event.select().where(Event.data['result'].has_key('success'))

remove(*keys)
    Remove one or more keys from the JSON object.

        # Atomically remove key:
        Event.update(data=Event.data.remove('result')).execute()

  HStore

Postgresql's hstore <https://www.postgresql.org/docs/current/hstore.html> extension stores arbitrary key/value pairs in a single column. Enable it by passing register_hstore=True when initializing the database:

    db = PostgresqlExtDatabase('my_app', register_hstore=True)
    class Event(Model):
        data = HStoreField()
        class Meta:
            database = db

HStoreField supports the following operations:

•   Store and retrieve arbitrary dictionaries

•   Filter by key(s) or partial dictionary

•   Update/add one or more keys to an existing dictionary

•   Delete one or more keys from an existing dictionary

•   Select keys, values, or zip keys and values

•   Retrieve a slice of keys/values

•   Test for the existence of a key

•   Test that a key has a non-NULL value

    Example:

        # Create a record with arbitrary attributes:
        Event.create(data={
            'type': 'register',
            'ip': '1.2.3.4',
            'email': 'charles@example.com',
            'result': 'success',
            'referrer': 'google.com'})
        Event.create(data={
            'type': 'login',
            'ip': '1.2.3.4',
            'email': 'charles@example.com',
            'result': 'success'})
        # Lookup nested values in the data:
        Event.select().where(Event.data['type'] == 'login')
        # Filter by a key/value pair:
        Event.select().where(Event.data.contains({'result': 'success'})
        # Filter by key existence:
        Event.select().where(Event.data.exists('referrer'))
        # Atomic update - adds new keys, updates existing ones:
        new_data = Event.data.update({
            'result': 'ok',
            'status': 'success'})
        (Event
         .update(data=new_data)
         .where(Event.data['result'] == 'success')
         .execute())
        # Atomic key deletion:
        (Event
         .update(data=Event.data.delete('referrer'))
         .where(Event.data['referrer'] == 'google.com')
         .execute())
        # Retrieve keys or values as a list:
        for event in Event.select(Event.id, Event.data.keys().alias('k')):
            print(event.id, event.k)
        # Prints:
        # 1 ['ip', 'type', 'email', 'result', 'status']
        # Retrieve a subset of data:
        query = (Event
                 .select(Event.id,
                         Event.data.slice('ip', 'email').alias('source'))
                 .order_by(Event.data['ip']))
        for event in query:
            print(event.id, event.source)
        # Prints:
        # 1 {'ip': '1.2.3.4', 'email': 'charles@example.com'}

  HStoreField API

class HStoreField

    Note: By default HStoreField will use a GiST index. To disable this, initialize the field with index=False.

__getitem__(key)

Parameters
    key (str) -- get value at given key.

    Example:

        Event.select().where(Event.data['type'] == 'login')

contains(value)

Parameters
    value (dict, list, tuple or string key.) -- value to search for.

    Test whether the HStore data contains the given dict (match keys and values), list/tuple (match keys), or str key.

    Example:

        # Contains key/value pairs:
        Event.select().where(Event.data.contains({'result': 'success'}))
        # Contains a list of keys:
        Event.select().where(Event.data.contains(['result', 'status']))
        # Contains a single key:
        Event.select().where(Event.data.contains('result'))

contains_any(*keys)
    Test whether the HStore contains any of the given keys.

exists(key)
    Test whether key exists in data.

defined(key)
    Test whether key is non-NULL in data.

update(__data=None, **data)

Parameters

•   __data (dict) -- Specify update as a dict.

•   data -- Specify update as keyword arguments.

    Perform an in-place, atomic update.

        # Atomic update - adds new keys, updates existing ones:
        new_data = Event.data.update({
            'result': 'ok',
            'status': 'success'})
        (Event
         .update(data=new_data)
         .where(Event.data['result'] == 'success')
         .execute())

delete(*keys)

Parameters
    keys -- one or more keys to delete from data.

        # Atomic key deletion:
        (Event
         .update(data=Event.data.delete('referrer'))
         .where(Event.data['referrer'] == 'google.com')
         .execute())

slice(*keys)

Parameters
    keys (str) -- keys to retrieve.

    Retrieve only the provided key/value pairs:

        query = (Event
                 .select(Event.id,
                         Event.data.slice('ip', 'email').alias('source'))
                 .order_by(Event.data['ip']))
        for event in query:
            print(event.id, event.source)
        # 1 {'ip': '1.2.3.4', 'email': 'charles@example.com'}

keys()
    Return the keys as a list.

        query = Event.select(Event.data.keys().alias('keys'))
        for event in query:
            print(event.keys)
        # ['ip', 'type', 'email', 'result', 'status']

values()
    Return the values as a list.

items()
    Return the key-value pairs as a 2-dimensional list.

        query = Event.select(Event.data.items().alias('items'))
        for event in query:
            print(event.items)
        # [['ip', '1.2.3.4'],
        #  ['type', 'register'],
        #  ['email', 'charles@example.com'],
        #  ['result', 'ok'],
        #  ['status', 'success']]

  Arrays

class ArrayField(field_class=IntegerField, field_kwargs=None, dimensions=1, convert_values=False)
    Stores a Postgresql array of the given field type.

Parameters

•   field_class -- a subclass of Field <#Field>, e.g. IntegerField <#IntegerField>.

•   field_kwargs (dict) -- arguments to initialize field_class.

•   dimensions (int) -- Number of array dimensions.

•   convert_values (bool) -- Apply field_class value conversion to retrieved data.

    Note: By default ArrayField will use a GIN index. To disable this, initialize the field with index=False.

    Example:

        class Post(Model):
            tags = ArrayField(CharField)
        Post.create(tags=['python', 'peewee', 'postgresql'])
        Post.create(tags=['python', 'sqlite'])
        # Get an item by index.
        Post.select(Post.tags[0].alias('first_tag'))
        # Get a slice:
        Post.select(Post.tags[:2].alias('first_two'))

    Multi-dimensional array example:

        class Outline(Model):
            points = ArrayField(IntegerField, dimensions=2)
        Outline.create(points=[[1, 1], [1, 5], [5, 5], [5, 1]])

contains(*items)
    Filter rows where the array contains all of the given values.

Parameters
    items -- One or more items that must be in the given array field.

        Post.select().where(Post.tags.contains('postgresql', 'python'))

contains_any(*items)
    Filter rows where the array contains any of the given values.

Parameters
    items -- One or more items to search for in the given array field.

        Post.select().where(Post.tags.contains('postgresql', 'python'))

  Interval

class IntervalField(**kwargs)
    Stores Python datetime.timedelta instances using Postgresql's native INTERVAL type.

        from datetime import timedelta
        class Subscription(Model):
            duration = IntervalField()
        Subscription.create(duration=timedelta(days=30))
        (Subscription
         .select()
         .where(Subscription.duration > timedelta(days=10)))

  DateTimeTZ Field

class DateTimeTZField(**kwargs)
    Timezone-aware datetime field using Postgresql's TIMESTAMP WITH TIME ZONE type.

        class Event(Model):
            timestamp = DateTimeTZField()
        now = datetime.datetime.now().astimezone(datetime.timezone.utc)
        Event.create(timestamp=now)
        event = Event.get()
        print(event.timestamp)
        # 2026-01-02 03:04:05.012345+00:00

  Full-Text Search

Postgresql full-text search uses the tsvector and tsquery types. Peewee offers two approaches: the simple Match() function (no schema changes required) and the TSVectorField for dedicated search columns (better performance).

Simple approach - no schema changes required:

    from playhouse.postgres_ext import Match
    def search_posts(term):
        return Post.select().where(
            (Post.status == 'published') &
            Match(Post.body, term))

The Match() function will automatically convert the left-hand operand to a tsvector, and the right-hand operand to a tsquery. For better performance, create a GIN index:

    CREATE INDEX posts_fts ON post USING gin(to_tsvector('english', body));

Dedicated column - better performance:

    class Post(Model):
        body = TextField()
        search_content = TSVectorField()  # Automatically gets a GIN index.
    # Store a post and populate the search vector:
    Post.create(
        body=body_text,
        search_content=fn.to_tsvector(body_text))
    # Search:
    Post.select().where(Post.search_content.match('python postgresql'))
    # Search using expressions:
    terms = 'python & (sqlite | postgres)'
    Post.select().where(Post.search_content.match(terms))

For more information, see the Postgres full-text search docs <https://www.postgresql.org/docs/current/textsearch.html>.

Match(field, query)
    Generate a full-text search expression that converts field to tsvector and query to tsquery automatically.

class TSVectorField
    Field type for storing pre-computed tsvector data. Automatically created with a GIN index (use index=False to disable).

    Note: Data must be explicitly converted to tsvector on write using fn.to_tsvector().

    Example:

        class Post(Model):
            body = TextField()
            search_content = TSVectorField()
        Post.create(
            body=body_text,
            search_content=fn.to_tsvector(body_text))
        (Post
         .select()
         .where(Post.search_content.match('python & (sqlite | postgres)')))

match(query, language=None, plain=False)

Parameters

•   query (str) -- Full-text search query.

•   language (str) -- Optional language name.

•   plain (bool) -- Use the plain (simple) query parser instead of the default one, which supports &, |, and ! operators.

  Server-Side Cursors

For large result sets, server-side (named) cursors stream rows from the server rather than loading the entire result into memory. Rows are fetched transparently from the server as you iterate.

Refer to your driver documentation for details:

•   psycopg2 server-side cursors <https://www.psycopg.org/docs/usage.html#server-side-cursors>

•   psycopg3 server-side cursors <https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#server-side-cursors>

    Note: To use server-side (or named) cursors, you must be using PostgresqlExtDatabase.

    Wrap any SELECT query with ServerSide():

        from playhouse.postgres_ext import ServerSide
        # Must be in a transaction to use server-side cursors.
        with db.atomic():
            # Create a normal SELECT query.
            large_query = PageView.select()
            # Then wrap in `ServerSide` and iterate.
            for page_view in ServerSide(large_query):
                # Do something interesting.
                pass
            # At this point server side resources are released.

    For more granular control or to close the cursor explicitly:

        with db.atomic():
            large_query = PageView.select().order_by(PageView.id.desc())
            # Rows will be fetched 1000 at-a-time, but iteration is transparent.
            query = ServerSideQuery(query, array_size=1000)
            # Read 9500 rows then close server-side cursor.
            accum = []
            for i, obj in enumerate(query.iterator()):
                if i == 9500:
                    break
                accum.append(obj)
            # Release server-side resource.
            query.close()

    Warning: Server-side cursors live only within a transaction. If you are using psycopg2 (not psycopg3), cursors are declared WITH HOLD and must be fully exhausted or explicitly closed to release server resources.

ServerSide(select_query)

Parameters
    select_query -- a SelectQuery <#SelectQuery> instance.

Rtype generator

    Wrap select_query in a transaction and iterate using iterator() <#BaseQuery.iterator> (disables row caching).

  CockroachDB

CockroachDB <https://www.cockroachlabs.com> (CRDB) is compatible with Postgresql's wire protocol and is well-supported by Peewee. Use the dedicated CockroachDatabase class rather than PostgresqlDatabase <#PostgresqlDatabase> to get CRDB-specific handling.

    from playhouse.cockroachdb import CockroachDatabase
    db = CockroachDatabase('my_app', user='root', host='10.8.0.1')

If you are using Cockroach Cloud <https://cockroachlabs.cloud/>, you may find it easier to specify the connection parameters using a connection-string:

    db = CockroachDatabase('postgresql://root:secret@host:26257/defaultdb...')

SSL configuration:

    db = CockroachDatabase(
        'my_app',
        user='root',
        host='10.8.0.1',
        sslmode='verify-full',
        sslrootcert='/path/to/root.crt')
    # Or, alternatively, specified as part of a connection-string:
    db = CockroachDatabase('postgresql://root:secret@host:26257/dbname'
                           '?sslmode=verify-full&sslrootcert=/path/to/root.crt'
                           '&options=--cluster=my-cluster-xyz')

  Key differences from Postgresql

•   No nested transactions. CRDB does not support savepoints, so calling atomic() <#Database.atomic> inside another atomic() block raises an exception. Use transaction() <#Database.transaction> instead, which ignores nested calls and commits only when the outermost block exits.

•   Client-side retries. CRDB may abort transactions due to contention. Use run_transaction() for automatic retries.

    Special field-types that may be useful when using CRDB:

•   UUIDKeyField - a primary-key field implementation that uses CRDB's UUID type with a default randomly-generated UUID.

•   RowIDField - a primary-key field implementation that uses CRDB's INT type with a default unique_rowid().

•   JSONField - same as the Postgres BinaryJSONField, as CRDB treats all JSON as JSONB.

•   ArrayField - same as the Postgres extension (but does not support multi-dimensional arrays).

    Transactions:

        # transaction() is safe to nest; the outer block manages the commit.
        @db.transaction()
        def create_user(username):
            return User.create(username=username)
        with db.transaction():
            create_user('alice')   # Nested call is folded into outer transaction.
            create_user('bob')
        # Transaction is committed here.

    Client-side retries:

        from playhouse.cockroachdb import CockroachDatabase
        db = CockroachDatabase('my_app')
        def transfer_funds(from_id, to_id, amt):
            """
            Returns a 3-tuple of (success?, from balance, to balance). If there are
            not sufficient funds, then the original balances are returned.
            """
            def thunk(db_ref):
                src, dest = (Account
                             .select()
                             .where(Account.id.in_([from_id, to_id])))
                if src.id != from_id:
                    src, dest = dest, src  # Swap order.
                # Cannot perform transfer, insufficient funds!
                if src.balance < amt:
                    return False, src.balance, dest.balance
                # Update each account, returning the new balance.
                src, = (Account
                        .update(balance=Account.balance - amt)
                        .where(Account.id == from_id)
                        .returning(Account.balance)
                        .execute())
                dest, = (Account
                         .update(balance=Account.balance + amt)
                         .where(Account.id == to_id)
                         .returning(Account.balance)
                         .execute())
                return True, src.balance, dest.balance
            # Perform the queries that comprise a logical transaction. In the
            # event the transaction fails due to contention, it will be auto-
            # matically retried (up to 10 times).
            return db.run_transaction(thunk, max_attempts=10)

  CRDB API

class CockroachDatabase(database, **kwargs)
    Subclass of PostgresqlDatabase <#PostgresqlDatabase> for CockroachDB.

run_transaction(callback, max_attempts=None, system_time=None, priority=None)

Parameters

•   callback -- Callable accepting a single db argument. Must not manage the transaction itself. May be called multiple times.

•   max_attempts (int) -- Retry limit.

•   system_time (datetime) -- Execute AS OF SYSTEM TIME with respect to the given value.

•   priority (str) -- 'low', 'normal', or 'high'.

Raises
    ExceededMaxAttempts -- When max_attempts is exceeded.

    Execute SQL in a transaction with automatic client-side retries.

    User-provided callback:

•   Must accept one parameter, the db instance representing the connection the transaction is running under.

•   Must not attempt to commit, rollback or otherwise manage the transaction.

•   May be called more than one time.

•   Should ideally only contain SQL operations.

    Additionally, the database must not have any open transactions at the time this function is called, as CRDB does not support nested transactions. Attempting to do so will raise a NotImplementedError.

class PooledCockroachDatabase(database, **kwargs)
    Connection-pooling variant of CockroachDatabase.

run_transaction(db, callback, max_attempts=None, system_time=None, priority=None)
    Run SQL in a transaction with automatic client-side retries. See CockroachDatabase.run_transaction() for details.

    Note: This function is equivalent to the identically-named method on the CockroachDatabase class.

    CRDB-specific field types:

class UUIDKeyField
    UUID primary key auto-populated with CRDB's gen_random_uuid().

class RowIDField
    Integer primary key auto-populated with CRDB's unique_rowid().

  MySQL and MariaDB

Peewee provides alternate drivers for MySQL through playhouse.mysql_ext.

class MySQLConnectorDatabase(database, **kwargs)
    Database implementation using the official mysql-connector-python <https://dev.mysql.com/doc/connector-python/en/> driver instead of pymysql.

        from playhouse.mysql_ext import MySQLConnectorDatabase
        db = MySQLConnectorDatabase('my_db', host='1.2.3.4', user='mysql')

class PooledMySQLConnectorDatabase(database, **kwargs)
    Connection-pooling variant of MySQLConnectorDatabase.

class MariaDBConnectorDatabase(database, **kwargs)
    Database implementation using the mariadb-connector <https://mariadb-corporation.github.io/mariadb-connector-python/> driver.

    Note: Does not accept charset, sql_mode, or use_unicode parameters (charset is always utf8mb4).

        from playhouse.mysql_ext import MariaDBConnectorDatabase
        db = MariaDBConnectorDatabase('my_db', host='1.2.3.4', user='mysql')

class PooledMariaDBConnectorDatabase(database, **kwargs)
    Connection-pooling variant of MariaDBConnectorDatabase.

    MySQL-specific helpers:

class JSONField
    Extends TextField <#TextField> with transparent JSON encoding/decoding.

extract(path)
    Extract a value from a JSON document at the given JSON path (e.g. '$.key').

Match(columns, expr, modifier=None)
    Helper for MySQL full-text search using MATCH ... AGAINST syntax.

Parameters

•   columns -- A single Field <#Field> or a tuple of fields.

•   expr (str) -- Full-text search expression.

•   modifier (str) -- Optional modifier, e.g. 'IN BOOLEAN MODE'.

        from playhouse.mysql_ext import Match
        Post.select().where(
            Match((Post.title, Post.body), 'python asyncio',
                  modifier='IN BOOLEAN MODE'))

  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

•   Connection Pooling

•   Schema Migrations

•   Reflection

•   pwiz - Model Generator

•   Test Utilities

  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
sqlite:///path | SqliteDatabase <#SqliteDatabase>
postgres:// | PostgresqlDatabase <#PostgresqlDatabase>
postgresext:// | PostgresqlExtDatabase <#playhouse.postgres_ext.PostgresqlExtDatabase>
mysql:// | MySQLDatabase <#MySQLDatabase>

Connection pool implementations:
Scheme | Database class
sqlite+pool:///path | PooledSqliteDatabase
postgres+pool:// | PooledPostgresqlDatabase
postgresext+pool:// | PooledPostgresqlExtDatabase <#playhouse.postgres_ext.PooledPostgresqlExtDatabase>
mysql+pool:// | PooledMySQLDatabase

Alternate drivers:
Scheme | Database class
psycopg3:// | Psycopg3Database <#playhouse.postgres_ext.Psycopg3Database>
psycopg3+pool:// | PooledPsycopg3Database <#playhouse.postgres_ext.PooledPsycopg3Database>
cockroachdb:// | CockroachDatabase <#playhouse.cockroachdb.CockroachDatabase>
cockroachdb+pool:// | PooledCockroachDatabase <#playhouse.cockroachdb.PooledCockroachDatabase>
cysqlite:// | CySqliteDatabase <#playhouse.cysqlite_ext.CySqliteDatabase>
cysqlite+pool:// | PooledCySqliteDatabase <#playhouse.cysqlite_ext.PooledCySqliteDatabase>
apsw:// | APSWDatabase <#playhouse.apsw_ext.APSWDatabase>
mariadbconnector:// | MariaDBConnectorDatabase <#playhouse.mysql_ext.MariaDBConnectorDatabase>
mariadbconnector+pool:// | PooledMariaDBConnectorDatabase <#playhouse.mysql_ext.PooledMariaDBConnectorDatabase>
mysqlconnector:// | MySQLConnectorDatabase <#playhouse.mysql_ext.MySQLConnectorDatabase>
mysqlconnector+pool:// | PooledMySQLConnectorDatabase <#playhouse.mysql_ext.PooledMySQLConnectorDatabase>

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 url and return an appropriate Database <#Database> instance.

    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://user:password@host:5432/dbname

•   mysql://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, and password keys 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 <#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(FirebirdDatabase, 'firebird')
        db = connect('firebird://my-firebird-db')

  Connection Pooling

The playhouse.pool module contains a number of Database <#Database> classes that provide connection pooling for Postgresql, MySQL and SQLite databases. The pool works by overriding the methods on the Database <#Database> class that open and close connections to the backend.

In multi-threaded applications, each thread gets its own connection; the pool maintains up to max_connections open connections at any time. In single-threaded applications, a single connection is recycled.

The application only needs to ensure that connections are closed when work is done - typically at the end of an HTTP request. 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:

•   PooledPostgresqlDatabase

•   PooledMySQLDatabase

•   PooledSqliteDatabase

    Additional implementations:

•   playhouse.cysqlite_ext - PooledCySqliteDatabase

•   playhouse.mysql_ext - PooledMariaDBConnectorDatabase

•   playhouse.mysql_ext - PooledMySQLConnectorDatabase

•   playhouse.postgres_ext - PooledPostgresqlExtDatabase

•   playhouse.postgres_ext - PooledPsycopg3Database

•   playhouse.cockroachdb - PooledCockroachDatabase

    Note: Applications using Peewee's asyncio integration <#asyncio> do not need to use a special pooled database - the Async databases use a connection pool by default.

class PooledDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)
    Mixin class mixed into the specific backend subclasses above.

Parameters

•   database (str) -- The name of the database or database file.

•   max_connections (int) -- Maximum number of concurrent connections. Pass None for 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. 0 blocks indefinitely; None (default) raises immediately.

    Note: Connections will not be closed exactly when they exceed their stale_timeout. Instead, stale connections are only closed when a new connection is requested.

    Note: If the pool is exhausted and no timeout is configured, a ValueError is 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 age seconds. 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 <#SqliteDatabase>.

class PooledPostgresqlDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)
    Pool implementation for Postgresql databases. Extends PostgresqlDatabase <#PostgresqlDatabase>.

class PooledMySQLDatabase(database, max_connections=20, stale_timeout=None, timeout=None, **kwargs)
    Pool implementation for MySQL / MariaDB databases. Extends MySQLDatabase <#MySQLDatabase>.

  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 fragile, brittle and unnecessarily complex. Migrations can be written as simple python scripts and executed from the command-line. Since the migrations only depend on your application's Database <#Database> object, migration scripts to not introduce new dependencies.

Supported operations:

•   Add, rename, or drop columns.

•   Make columns nullable or not nullable.

•   Change a column's type.

•   Rename a table.

•   Add or drop indexes and constraints.

•   Add or drop column default values.

    See also: Schema Management <#schema>

        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 db.atomic() to ensure changes are not partially applied.

  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'))

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),  # Normal index.
        # Create a unique index on the category and title fields.
        migrator.add_index('story', ('category_id', 'title'), True),  # Unique.
        # Drop the pub-date + status index.
        migrator.drop_index('story', 'story_pub_date_status'),
    )

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 (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', 'now()'))
    # Remove a default:
    migrate(migrator.drop_column_default('entry', 'status'))

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 Database <#Database> instance.

    The SchemaMigrator is responsible for generating schema-altering statements.

classmethod from_database(database)

Parameters
    database (Database <#Database>) -- database instance to generate migrations for.

Returns
    SchemaMigrator instance appropriate to provided database.

    Factory method that returns the appropriate SchemaMigrator subclass for the given database.

add_column(table, column_name, field)

Parameters

•   table (str) -- Name of the table to add column to.

•   column_name (str) -- Name of the new column.

•   field (Field <#Field>) -- A Field <#Field> instance.

    Add a new column to the provided table. The field provided will be used to generate the appropriate column definition.

    If the field is not nullable it must specify a default value.

    Note: For non-null columns, the following occurs:

1.  column is added as allowing NULLs

2.  UPDATE query is executed to populate the default value

3.  column is changed to NOT NULL

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 default='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

•   table (str) -- Name of the table.

•   column_name (str) -- Name of the column to modify.

•   field (Field <#Field>) -- Field <#Field> instance representing new data type.

•   cast -- (postgres-only) specify a cast expression if the data-types are incompatible, e.g. column_name::int. Can be provided as either a string or a Cast <#Cast> instance.

    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.

add_index(table, columns, unique=False, using=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.

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.

add_constraint(table, name, constraint)

Parameters

•   table (str) -- Table to add constraint to.

•   name (str) -- Name used to identify the constraint.

•   constraint -- either a Check() <#Check> constraint or for adding an arbitrary constraint use SQL <#SQL>.

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 has limited support for ALTER TABLE queries, so the following operations are currently not supported for SQLite:

•   add_constraint

•   drop_constraint

•   add_unique

class MySQLMigrator(database)
    MySQL-specific subclass.

  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 <#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>) -- database instance to introspect.

•   schema (str) -- optional schema to introspect.

•   options -- arbitrary options, see Introspector.generate_models() for details.

Returns
    a dict mapping 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 TABLE SQL for a model class (without indexes or constraints):

        >>> 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 method from_database().

classmethod from_database(database, schema=None)

Parameters

•   database -- a Database <#Database> instance.

•   schema (str) -- an optional schema (supported by some databases).

    Creates an Introspector instance 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 BareField <#BareField> for 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 Model <#Model> class.

  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:
    python -m pwiz -e postgresql -u postgres my_db > models.py
    # Introspect a SQLite database:
    python -m pwiz -e sqlite path/to/my.db
    # Introspect a MySQL database (prompts for password):
    python -m pwiz -e mysql -u root -P my_db
    # Introspect only specific tables:
    python -m pwiz -e postgresql my_db -t user,tweet,follow

Command-line options:
Option | Meaning | Example
-e | Database backend | -e mysql
-H | Host | -H 10.0.0.1
-p | Port | -p 5432
-u | Username | -u postgres
-P | Password (prompts interactively) |
-s | Schema | -s public
-t | Comma-separated list of tables to include | -t user,tweet
-v | Include views |
-i | Embed database info as a comment |
-o | Preserve original column order |
-I | Ignore fields whose type is unknown |
-L | Use legacy table and column naming |

Valid -e values: sqlite, mysql, postgresql.

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 <#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 only SELECT queries.

        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 a list of (sql, params) 2-tuples for each query executed.

assert_query_count(expected, only_select=False)
    Decorator or context manager that raises AssertionError if the number of queries executed does not match expected.

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

  ORM Utilities

These modules provide higher-level abstractions on top of Peewee's core ORM and work with any database backend.

  On this page

•   Shortcuts

•   Hybrid Attributes

•   Key/Value Store

•   Signals

•   DataSet

•   Extra Field Types

•   Flask Utilities

  Shortcuts

playhouse.shortcuts provides helpers for serializing model instances to and from dictionaries, resolving compound queries, and thread-safe database swapping.

  Model Serialization

model_to_dict(model, recurse=True, backrefs=False, only=None, exclude=None, extra_attrs=None, fields_from_query=None, max_depth=None, manytomany=False)
    Convert a model instance to a dictionary.

Parameters

•   recurse (bool) -- Follow foreign keys and include the related object as a nested dict (default: True).

•   backrefs (bool) -- Follow back-references and include related collections as nested lists of dicts.

•   only -- A list or set of field instances to include exclusively.

•   exclude -- A list or set of field instances to exclude.

•   extra_attrs -- A list of attribute or method names to include in the output dict.

•   fields_from_query (Select <#Select>) -- Restrict serialization to only the fields that were explicitly selected in the generating query.

•   max_depth (int) -- Maximum depth when following relations.

•   manytomany (bool) -- Include many-to-many fields.

    Examples:

        user = User.create(username='alice')
        model_to_dict(user)
        # {'id': 1, 'username': 'alice'}
        model_to_dict(user, backrefs=True)
        # {'id': 1, 'username': 'alice', 'tweets': []}
        t = Tweet.create(user=user, content='hello')
        model_to_dict(t)
        # {'id': 1, 'content': 'hello', 'user': {'id': 1, 'username': 'alice'}}
        model_to_dict(t, recurse=False)
        # {'id': 1, 'content': 'hello', 'user': 1}
        model_to_dict(user, backrefs=True)
        # {'id': 1, 'tweets': [{'id': 1, 'content': 'hello'}], 'username': 'alice'}

    Note: If your use case is unusual, write a small custom function rather than trying to coerce model_to_dict with a complex combination of parameters.

dict_to_model(model_class, data, ignore_unknown=False)
    Construct a model instance from a dictionary. Foreign keys may be provided as nested dicts; back-references as lists of dicts.

Parameters

•   model_class (Model <#Model>) -- The model class to construct.

•   data (dict) -- A dictionary of data. Foreign keys can be included as nested dictionaries, and back-references as lists of dictionaries.

•   ignore_unknown (bool) -- Allow keys that do not correspond to any field on the model.

        user = dict_to_model(User, {'id': 1, 'username': 'alice'})
        user.username   # 'alice'
        # Nested foreign key:
        tweet = dict_to_model(Tweet, {
            'id': 1, 'content': 'hi',
            'user': {'id': 1, 'username': 'alice'}})
        tweet.user.username   # 'alice'

update_model_from_dict(instance, data, ignore_unknown=False)
    Update an existing model instance with values from a dictionary. Follows the same rules as dict_to_model().

Parameters

•   instance (Model <#Model>) -- The model instance to update.

•   data (dict) -- A dictionary of data. Foreign keys can be included as nested dictionaries, and back-references as lists of dictionaries.

•   ignore_unknown (bool) -- Allow keys that do not correspond to any field on the model.

  Compound Query Resolution

resolve_multimodel_query(query, key='_model_identifier')
    Resolve rows from a compound UNION or similar query to the correct model class. Useful when two tables are unioned and you need each row as an instance of the appropriate model.

Parameters

•   query -- A compound SelectQuery <#SelectQuery>.

•   key (str) -- Name of the column used to identify the model.

Returns
    An iterable that yields properly typed model instances.

  Thread-Safe Database Swapping

class ThreadSafeDatabaseMetadata
    Model Metadata <#Metadata> implementation that enables the database attribute to safely changed in a multi-threaded application. Use this when your application may swap the active database (e.g. primary / read replica) at runtime across threads:

        from playhouse.shortcuts import ThreadSafeDatabaseMetadata
        primary  = PostgresqlDatabase('main')
        replica  = PostgresqlDatabase('replica')
        class BaseModel(Model):
            class Meta:
                database = primary
                model_metadata_class = ThreadSafeDatabaseMetadata
        # Safe to do at runtime from any thread:
        BaseModel._meta.database = replica

  Hybrid Attributes

A hybrid attribute behaves differently depending on whether it is accessed on a model instance (executes Python logic) or on the model class (generates a SQL expression). This lets you write Python methods that work both as Python computations and as composable SQL clauses.

The concept is borrowed from SQLAlchemy's hybrid extension <https://docs.sqlalchemy.org/en/14/orm/extensions/hybrid.html>.

    from playhouse.hybrid import hybrid_property, hybrid_method
    class Interval(Model):
        start = IntegerField()
        end = IntegerField()
        @hybrid_property
        def length(self):
            return self.end - self.start
        @hybrid_method
        def contains(self, point):
            return (self.start <= point) & (point < self.end)

On an instance, Python arithmetic runs:

    i = Interval(start=1, end=5)
    i.length  # 4 (Python arithmetic)
    i.contains(3)  # True (Python comparison)

On the class, SQL is generated:

    Interval.select().where(Interval.length > 5)
    # WHERE ("end" - "start") > 5
    Interval.select().where(Interval.contains(2))
    # WHERE ("start" <= 2) AND (2 < "end")

When the Python and SQL implementations differ, provide a separate expression override:

    class Interval(Model):
        start = IntegerField()
        end = IntegerField()
        @hybrid_property
        def radius(self):
            return abs(self.length) / 2  # Python: uses Python abs()
        @radius.expression
        def radius(cls):
            return fn.ABS(cls.length) / 2  # SQL: uses fn.ABS()

Example:

    query = Interval.select().where(Interval.radius < 3)

This query is equivalent to the following SQL:

    SELECT "t1"."id", "t1"."start", "t1"."end"
    FROM "interval" AS t1
    WHERE ((abs("t1"."end" - "t1"."start") / 2) < 3)

class hybrid_property(fget, fset=None, fdel=None, expr=None)
    Decorator for defining a property with separate instance and class behaviors. Use @prop.expression to specify the SQL form when it differs from the Python form.

    Examples:

        class Interval(Model):
            start = IntegerField()
            end = IntegerField()
            @hybrid_property
            def length(self):
                return self.end - self.start
            @hybrid_property
            def radius(self):
                return abs(self.length) / 2
            @radius.expression
            def radius(cls):
                return fn.ABS(cls.length) / 2

    When accessed on an Interval instance, the length and radius properties will behave as you would expect. When accessed as class attributes, though, a SQL expression will be generated instead:

        query = (Interval
                 .select()
                 .where(
                     (Interval.length > 6) &
                     (Interval.radius >= 3)))

    Would generate the following SQL:

        SELECT "t1"."id", "t1"."start", "t1"."end"
        FROM "interval" AS t1
        WHERE (
            (("t1"."end" - "t1"."start") > 6) AND
            ((abs("t1"."end" - "t1"."start") / 2) >= 3)
        )

class hybrid_method(func, expr=None)
    Decorator for defining a method with separate instance and class behaviors. Use @method.expression to specify the SQL form.

    Example:

        class Interval(Model):
            start = IntegerField()
            end = IntegerField()
            @hybrid_method
            def contains(self, point):
                return (self.start <= point) & (point < self.end)

    When called with an Interval instance, the contains method will behave as you would expect. When called as a classmethod, though, a SQL expression will be generated:

        query = Interval.select().where(Interval.contains(2))

    Would generate the following SQL:

        SELECT "t1"."id", "t1"."start", "t1"."end"
        FROM "interval" AS t1
        WHERE (("t1"."start" <= 2) AND (2 < "t1"."end"))

  Key/Value Store

playhouse.kv.KeyValue provides a persistent dictionary backed by a Peewee database instance.

    from playhouse.kv import KeyValue
    KV = KeyValue()   # Defaults to an in-memory SQLite database.
    KV['k1'] = 'v1'
    KV.update(k2='v2', k3='v3')
    assert KV['k2'] == 'v2'
    print(dict(KV))   # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
    # Expression-based access:
    for value in KV[KV.key > 'k1']:
        print(value)    # 'v2', 'v3'
    # Expression-based bulk update:
    KV[KV.key > 'k1'] = 'updated'
    # Expression-based deletion:
    del KV[KV.key > 'k1']

class KeyValue(key_field=None, value_field=None, ordered=False, database=None, table_name='keyvalue')

Parameters

•   key_field (Field <#Field>) -- Field for the key. Defaults to CharField <#CharField>. Must specify primary_key=True.

•   value_field (Field <#Field>) -- Field for the value. Defaults to PickleField.

•   ordered (bool) -- Return keys in sorted order when iterating.

•   database (Database <#Database>) -- Database to use. Defaults to an in-memory SQLite database.

•   table_name (str) -- Name of the underlying table.

    The table is created automatically on construction if it does not exist. Supports the standard dictionary interface plus expression-based access.

__contains__(expr)

Parameters
    expr -- a single key or an expression

Returns
    Boolean whether key/expression exists.

    Example:

        kv = KeyValue()
        kv.update(k1='v1', k2='v2')
        'k1' in kv  # True
        'kx' in kv  # False
        (KV.key < 'k2') in KV  # True
        (KV.key > 'k2') in KV  # False

__len__()

Returns
    Count of items stored.

__getitem__(expr)

Parameters
    expr -- a single key or an expression.

Returns
    value(s) corresponding to key/expression.

Raises
    KeyError if single key given and not found.

    Examples:

        KV = KeyValue()
        KV.update(k1='v1', k2='v2', k3='v3')
        KV['k1']  # 'v1'
        KV['kx']  # KeyError: "kx" not found
        KV[KV.key > 'k1']  # ['v2', 'v3']
        KV[KV.key < 'k1']  # []

__setitem__(expr, value)

Parameters

•   expr -- a single key or an expression.

•   value -- value to set for key(s)

    Set value for the given key. If expr is an expression, then any keys matching the expression will have their value updated.

    Example:

        KV = KeyValue()
        KV.update(k1='v1', k2='v2', k3='v3')
        KV['k1'] = 'v1-x'
        print(KV['k1'])  # 'v1-x'
        KV[KV.key >= 'k2'] = 'v99'
        print(dict(KV))  # {'k1': 'v1-x', 'k2': 'v99', 'k3': 'v99'}

__delitem__(expr)

Parameters
    expr -- a single key or an expression.

    Delete the given key. If an expression is given, delete all keys that match the expression.

    Example:

        KV = KeyValue()
        KV.update(k1=1, k2=2, k3=3)
        del KV['k1']  # Deletes "k1".
        del KV['k1']  # KeyError: "k1" does not exist
        del KV[KV.key > 'k2']  # Deletes "k3".
        del KV[KV.key > 'k99']  # Nothing deleted, no keys match.

keys()

Returns
    an iterable of all keys in the table.

values()

Returns
    an iterable of all values in the table.

items()

Returns
    an iterable of all key/value pairs in the table.

update(__data=None, **mapping)
    Efficiently bulk-insert or replace the given key/value pairs.

    Example:

        KV = KeyValue()
        KV.update(k1=1, k2=2)  # Sets 'k1'=1, 'k2'=2.
        print(dict(KV))  # {'k1': 1, 'k2': 2}
        KV.update(k2=22, k3=3)  # Updates 'k2'->22, sets 'k3'=3.
        print(dict(KV))  # {'k1': 1, 'k2': 22, 'k3': 3}
        KV.update({'k2': -2, 'k4': 4})  # Also can pass a dictionary.
        print(dict(KV))  # {'k1': 1, 'k2': -2, 'k3': 3, 'k4': 4}

get(expr, default=None)

Parameters

•   expr -- a single key or an expression.

•   default -- default value if key not found.

Returns
    value of given key/expr or default if single key not found.

    Get the value at the given key. If the key does not exist, the default value is returned, unless the key is an expression in which case an empty list will be returned.

pop(expr, default=Sentinel)

Parameters

•   expr -- a single key or an expression.

•   default -- default value if key does not exist.

Returns
    value of given key/expr or default if single key not found.

    Get value and delete the given key. If the key does not exist, the default value is returned, unless the key is an expression in which case an empty list is returned.

clear()
    Remove all items from the key-value table.

  Signals

playhouse.signals adds Django-style model lifecycle signals. Models must subclass playhouse.signals.Model (not peewee.Model) for hooks to fire.

    from playhouse.signals import Model, post_save
    class MyModel(Model):
        data = IntegerField()
        class Meta:
            database = db
    @post_save(sender=MyModel)
    def on_save(model_class, instance, created):
        if created:
            notify_new(instance)

The following signals are provided:

pre_save
    Called immediately before an object is saved to the database. Provides an additional keyword argument created, indicating whether the model is being saved for the first time or updated.

post_save
    Called immediately after an object is saved to the database. Provides an additional keyword argument created, indicating whether the model is being saved for the first time or updated.

pre_delete
    Called immediately before an object is deleted from the database when Model.delete_instance() <#Model.delete_instance> is used.

post_delete
    Called immediately after an object is deleted from the database when Model.delete_instance() <#Model.delete_instance> is used.

pre_init
    Called when a model class is first instantiated

    Warning: Signals fire only through the high-level instance methods (save() <#Model.save>, delete_instance() <#Model.delete_instance>). Bulk operations via insert() <#Model.insert>, update() <#Model.update>, and delete() <#Model.delete> do not trigger signals because no model instance is involved.

  Connecting handlers

Whenever a signal is dispatched, it will call any handlers that have been registered. This allows totally separate code to respond to events like model save and delete.

The Signal class provides a connect() method, which takes a callback function and two optional parameters for "sender" and "name". If specified, the "sender" parameter should be a single model class and allows your callback to only receive signals from that one model class. The "name" parameter is used as a convenient alias in the event you wish to unregister your signal handler.

Example:

    @post_save(sender=MyModel, name='project.cache_buster')
    def cache_bust(sender, instance, created):
        cache.delete(make_cache_key(instance))

Or connect manually:

    def on_delete(sender, instance):
        audit_log(instance)
    pre_delete.connect(on_delete, sender=MyModel)

Disconnect by name or reference:

    post_save.disconnect(name='project.cache_buster')
    pre_delete.disconnect(on_delete)

Signal callback signature:

•   pre_init(sender, instance)

•   pre_save(sender, instance, created)

•   post_save(sender, instance, created)

•   pre_delete(sender, instance)

•   post_delete(sender, instance)

class Signal
    Stores a list of receivers (callbacks) and calls them when the "send" method is invoked.

connect(receiver, name=None, sender=None)

Parameters

•   receiver (callable) -- a callable that takes at least two parameters, a "sender", which is the Model subclass that triggered the signal, and an "instance", which is the actual model instance.

•   name (string) -- a short alias

•   sender (Model <#Model>) -- if specified, only instances of this model class will trigger the receiver callback.

    Add the receiver to the internal list of receivers, which will be called whenever the signal is sent.

        from playhouse.signals import post_save
        from project.handlers import cache_buster
        post_save.connect(cache_buster, name='project.cache_buster')

disconnect(receiver=None, name=None, sender=None)

param callable receiver
    the callback to disconnect

param string name
    a short alias

param Model sender
    disconnect model-specific handler.

    Disconnect the given receiver (or the receiver with the given name alias) so that it no longer is called. Either the receiver or the name must be provided.

        post_save.disconnect(name='project.cache_buster')

send(instance, *args, **kwargs)

Parameters
    instance -- a model instance

    Iterates over the receivers and will call them in the order in which they were connected. If the receiver specified a sender, it will only be called if the instance is an instance of the sender.

  DataSet

playhouse.dataset exposes a dict-oriented API for relational data, modeled after the dataset library <https://dataset.readthedocs.io/>. It is useful for quick scripts, data loading, and CSV/JSON import-export.

Basic operations:

    from playhouse.dataset import DataSet
    db = DataSet('sqlite:///data.db')
    # Access a table (created automatically if it doesn't exist):
    users = db['user']
    # Insert rows with any columns:
    users.insert(name='Alice', age=30)
    users.insert(name='Bob', age=25, active=True)  # New column added automatically.
    # Retrieve rows:
    alice = users.find_one(name='Alice')
    print(alice)   # {'id': 1, 'name': 'Alice', 'age': 30, 'active': None}
    for user in users:
        print(user['name'])
    for admin in users.find(active=True):
        print(admin['name'])  # Bob.
    # Update:
    users.update(name='Alice', age=31, columns=['name'])  # 'name' is the lookup.
    # Update all records:
    users.update(admin=False)
    # Delete:
    users.delete(name='Bob')

Export and import data:

    # Export to JSON:
    db.freeze(users.all(), format='json', filename='users.json')
    # Export CSV to stdout:
    db.freeze(users.all(), format='csv', file_obj=sys.stdout)
    # Import from CSV:
    db.thaw('user', format='csv', filename='import.csv')
    # Import a JSON file to a new table.
    db.thaw('new_table', format='json', filename='json-data.json')

Transactions:

    # Transactions.
    with db.transaction() as txn:
        users.insert(name='Charlie')
        with db.transaction() as nested_txn:
            table.update(name='Charlie', favorite_orm='sqlalchemy', columns=['name'])
            nested_txn.rollback()  # JK.

Introspection:

    print(db.tables)
    # ['new_table', 'user']
    print(db['user'].columns)
    # ['id', 'age', 'name', 'active', 'admin', 'favorite_orm']
    print(len(db['user']))
    # 2

class DataSet(url, **kwargs)

Parameters

•   url -- Database URLs <#db-url> or a Database <#Database> instance.

•   kwargs -- additional keyword arguments passed to Introspector.generate_models() when introspecting the db.

tables
    List of table names in the database (computed dynamically).

__getitem__(table_name)
    Return a Table <#Table> for the given name. Creates the table if it doesn't exist.

query(sql, params=None, commit=True)

Parameters

•   sql (str) -- A SQL query.

•   params (list) -- Optional parameters for the query.

•   commit (bool) -- Whether the query should be committed upon execution.

Returns
    A database cursor.

    Execute the provided query against the database.

transaction()
    Return a context manager representing a transaction.

freeze(query, format='csv', filename=None, file_obj=None, encoding='utf8', **kwargs)

Parameters

•   query -- A SelectQuery <#SelectQuery>, generated using all() or ~Table.find.

•   format -- Output format. By default, csv and json are supported.

•   filename -- Filename to write output to.

•   file_obj -- File-like object to write output to.

•   encoding (str) -- File encoding.

•   kwargs -- Arbitrary parameters for export-specific functionality.

    Export data to a file.

thaw(table, format='csv', filename=None, file_obj=None, strict=False, encoding='utf8', **kwargs)

Parameters

•   table (str) -- The name of the table to load data into.

•   format -- Input format. By default, csv and json are supported.

•   filename -- Filename to read data from.

•   file_obj -- File-like object to read data from.

•   strict (bool) -- Whether to store values for columns that do not already exist on the table.

•   encoding (str) -- File encoding.

•   kwargs -- Arbitrary parameters for import-specific functionality.

    Import data from a file into table. If strict=False (default), new columns are added automatically.

connect()

close()
    Open or close the underlying database connection.

class Table(dataset, name, model_class)
    Provides a high-level API for working with rows in a given table.

columns
    List of column names.

model_class
    A dynamically-created Model <#Model> class.

insert(**data)
    Insert a row, adding new columns as needed.

update(columns=None, conjunction=None, **data)
    Update the table using the provided data. If one or more columns are specified in the columns parameter, then those columns' values in the data dictionary will be used to determine which rows to update.

        # Update all rows.
        db['users'].update(favorite_orm='peewee')
        # Only update Huey's record, setting his age to 3.
        db['users'].update(name='Huey', age=3, columns=['name'])

find(**query)
    Return all rows matching equality conditions (all rows if no conditions given).

find_one(**query)
    Return the first matching row, or None.

all()
    Return all rows.

delete(**query)
    Delete matching rows (all rows if no conditions given).

create_index(columns, unique=False)
    Create an index on the given columns:

        # Create a unique index on the `username` column.
        db['users'].create_index(['username'], unique=True)

freeze(format='csv', filename=None, file_obj=None, **kwargs)

Parameters

•   format -- Output format. By default, csv and json are supported.

•   filename -- Filename to write output to.

•   file_obj -- File-like object to write output to.

•   kwargs -- Arbitrary parameters for export-specific functionality.

thaw(format='csv', filename=None, file_obj=None, strict=False, **kwargs)

Parameters

•   format -- Input format. By default, csv and json are supported.

•   filename -- Filename to read data from.

•   file_obj -- File-like object to read data from.

•   strict (bool) -- Whether to store values for columns that do not already exist on the table.

•   kwargs -- Arbitrary parameters for import-specific functionality.

  Extra Field Types

playhouse.fields provides two general-purpose field types.

class CompressedField(compression_level=6, algorithm='zlib', **kwargs)
    Stores compressed binary data using zlib or bz2. Extends BlobField <#BlobField>; compression and decompression are transparent:

        from playhouse.fields import CompressedField
        class LogEntry(Model):
            payload = CompressedField(algorithm='zlib', compression_level=9)

Parameters

•   compression_level (int) -- 0-9 (9 is maximum compression).

•   algorithm (str) -- 'zlib' or 'bz2'.

class PickleField
    Stores arbitrary Python objects by pickling them into a BlobField <#BlobField>.

        from playhouse.fields import PickleField
        class CachedResult(Model):
            data = PickleField()
        CachedResult.create(data={'nested': [1, 2, 3]})

  Flask Utilities

playhouse.flask_utils simplifies Peewee integration with Flask <https://flask.palletsprojects.com/>.

  FlaskDB Wrapper

FlaskDB handles three boilerplate tasks:

1.  Creates a Peewee database instance from Flask's app.config.

2.  Provides a Model base class whose Meta.database is wired to the Peewee instance.

3.  Registers before_request / teardown_request hooks that open and close a connection for every request.

    Basic setup:

        from flask import Flask
        from playhouse.flask_utils import FlaskDB
        app = Flask(__name__)
        app.config['DATABASE'] = 'postgresql://postgres:pw@localhost/my_app'
        db_wrapper = FlaskDB(app)
        class User(db_wrapper.Model):
            username = CharField(unique=True)
        class Tweet(db_wrapper.Model):
            user = ForeignKeyField(User, backref='tweets')
            content = TextField()

    Access the underlying Peewee database:

        peewee_db = db_wrapper.database
        @app.route('/transfer', methods=['POST'])
        def transfer():
            with peewee_db.atomic():
                # ... transactional logic ...
            return jsonify({'ok': True})

    Application factory pattern:

        db_wrapper = FlaskDB()
        class User(db_wrapper.Model):
            username = CharField(unique=True)
        def create_app():
            app = Flask(__name__)
            app.config['DATABASE'] = 'sqlite:///my_app.db'
            db_wrapper.init_app(app)
            return app

    Configuration via dict or a Database <#Database> instance directly:

        # Dictionary-based (uses playhouse.db_url under the hood):
        app.config['DATABASE'] = {
            'name': 'my_app',
            'engine': 'playhouse.pool.PooledPostgresqlDatabase',
            'user': 'postgres',
            'max_connections': 32,
        }
        # Pass a database object:
        peewee_db = PostgresqlExtDatabase('my_app')
        db_wrapper = FlaskDB(app, peewee_db)

    Excluding routes from connection management:

        app.config['FLASKDB_EXCLUDED_ROUTES'] = ('health_check', 'static')

class FlaskDB(app=None, database=None)

Parameters

•   app -- Flask application instance (optional; use init_app() for the factory pattern).

•   database -- A database URL string, configuration dictionary, or a Database <#Database> instance.

database
    The underlying Database <#Database> instance.

Model
    A base Model <#Model> class bound to this database instance.

init_app(app)
    Bind to a Flask application (factory pattern).

  Query Helpers

get_object_or_404(query_or_model, *query)

Parameters

•   query_or_model -- Either a Model <#Model> class or a pre-filtered SelectQuery <#SelectQuery>.

•   query -- Peewee filter expressions.

    Retrieve a single object matching the given query, or abort with HTTP 404 if no match is found.

        @app.route('/post/<slug>/')
        def post_detail(slug):
            post = get_object_or_404(
                Post.select().where(Post.published == True),
                Post.slug == slug)
            return render_template('post_detail.html', post=post)

object_list(template_name, query, context_variable='object_list', paginate_by=20, page_var='page', check_bounds=True, **kwargs)
    Paginate a query and render a template with the results.

Parameters

•   template_name (str) -- Template to render.

•   query -- SelectQuery <#SelectQuery> to paginate.

•   context_variable (str) -- Template variable name for the page of objects (default: 'object_list').

•   paginate_by (int) -- Items per page.

•   page_var (str) -- GET parameter name for the page number.

•   check_bounds (bool) -- Return 404 for invalid page numbers.

•   kwargs -- Extra template context variables.

    The template receives:

•   object_list (or context_variable) - page of objects.

•   page - current page number.

•   pagination - a PaginatedQuery instance.

        @app.route('/posts/')
        def post_list():
            return object_list(
                'post_list.html',
                query=Post.select().where(Post.published == True),
                paginate_by=10)

class PaginatedQuery(query_or_model, paginate_by, page_var='page', check_bounds=False)

Parameters

•   query_or_model -- Either a Model <#Model> or a SelectQuery <#SelectQuery> instance containing the collection of records you wish to paginate.

•   paginate_by -- Number of objects per-page.

•   page_var -- The name of the GET argument which contains the page.

•   check_bounds -- Whether to check that the given page is a valid page. If check_bounds is True and an invalid page is specified, then a 404 will be returned.

    Helper class to perform pagination based on GET arguments.

get_page()
    Return the current page number (1-based; defaults to 1).

get_page_count()
    Return the total number of pages.

get_object_list()
    Return the SelectQuery <#SelectQuery> for the requested page, with appropriate LIMIT and OFFSET applied. Returns a 404 if check_bounds=True and the page is empty.

  Recipes

Collected patterns for common real-world problems. Each recipe assumes familiarity with Querying <#querying>, Writing Data <#writing>, and Relationships and Joins <#relationships>.

All examples use the following models:

    import datetime
    from peewee import *
    db = SqliteDatabase(':memory:')
    class BaseModel(Model):
        class Meta:
            database = db
    class User(BaseModel):
        username = TextField(unique=True)
    class Tweet(BaseModel):
        user = ForeignKeyField(User, backref='tweets')
        content = TextField()
        timestamp = DateTimeField(default=datetime.datetime.now)
        created_date = DateTimeField(default=datetime.datetime.now)

  Optimistic Locking

Optimistic locking avoids holding a database lock across the read-modify-write cycle by recording a version number on each row. On write, the update is conditional on the version not having changed. If another process modified the row in the meantime, the update matches zero rows and the conflict is detected in application code.

This is a lighter-weight alternative to SELECT FOR UPDATE (Postgresql) or BEGIN IMMEDIATE (SQLite) when lock contention is expected to be low.

A reusable base class:

    class ConflictDetectedException(Exception):
        pass
    class BaseVersionedModel(BaseModel):
        version = IntegerField(default=1, index=True)
        def save_optimistic(self):
            if not self.id:
                # This is a new record, so the default logic is to perform an
                # INSERT. Ideally your model would also have a unique
                # constraint that made it impossible for two INSERTs to happen
                # at the same time.
                return self.save()
            # Update any data that has changed and bump the version counter.
            field_data = dict(self.__data__)
            current_version = field_data.pop('version', 1)
            self._populate_unsaved_relations(field_data)
            field_data = self._prune_fields(field_data, self.dirty_fields)
            if not field_data:
                raise ValueError('No changes have been made.')
            ModelClass = type(self)
            field_data['version'] = ModelClass.version + 1  # Atomic increment.
            updated = (ModelClass
                       .update(**field_data)
                       .where(
                           (ModelClass.version == current_version) &
                           (ModelClass.id == self.id))
                       .execute())
            if updated == 0:
                # No rows were updated, indicating another process has saved
                # a new version.
                raise ConflictDetectedException()
            else:
                # Increment local version to match what is now in the db.
                self.version += 1
                return True

Usage:

    class UserProfile(BaseVersionedModel):
        username = TextField(unique=True)
        bio = TextField(default='')
    >>> u = UserProfile(username='charlie')
    >>> u.save_optimistic()
    True
    >>> u.bio = 'Python developer'
    >>> u.save_optimistic()
    True
    >>> u.version
    2
    # Simulate a concurrent modification:
    >>> u2 = UserProfile.get(UserProfile.username == 'charlie')
    >>> u2.bio = 'Changed by another process'
    >>> u2.save_optimistic()
    True
    # The original instance's version is now stale:
    >>> u.bio = 'My update'
    >>> u.save_optimistic()
    ConflictDetectedException

  Get-or-Create Safely

get_or_create() <#Model.get_or_create> is convenient but has a small race window between the SELECT and the INSERT when the row does not yet exist. Two concurrent processes can both fail the SELECT and both attempt the INSERT, causing one to fail with an IntegrityError.

The safe pattern attempts the INSERT first and falls back to a GET on IntegrityError:

    def get_or_create_user(username):
        try:
            with db.atomic():
                return User.create(username=username), True
        except IntegrityError:
            return User.get(User.username == username), False
    user, created = get_or_create_user('charlie')

The db.atomic() wrapper is important: it ensures that the rollback on IntegrityError affects only this operation, not any surrounding transaction.

  Top Item Per Group

These examples find the single most recent tweet for each user. See Top N Per Group below for the generalized N-per-group problem.

The most portable approach uses a MAX() aggregate in a non-correlated subquery, then joins back to the tweet table on both user and timestamp:

    # When referencing a table multiple times, we'll call Model.alias() to create
    # a secondary reference to the table.
    TweetAlias = Tweet.alias()
    # Create a subquery that will calculate the maximum Tweet created_date for
    # each user.
    subquery = (TweetAlias
                .select(
                    TweetAlias.user,
                    fn.MAX(TweetAlias.created_date).alias('max_ts'))
                .group_by(TweetAlias.user)
                .alias('tweet_max'))
    # Query for tweets and join using the subquery to match the tweet's user
    # and created_date.
    query = (Tweet
             .select(Tweet, User)
             .join(User)
             .switch(Tweet)
             .join(subquery, on=(
                 (Tweet.created_date == subquery.c.max_ts) &
                 (Tweet.user == subquery.c.user_id))))

SQLite and MySQL permit a shorter form that groups by a subset of selected columns:

    query = (Tweet
             .select(Tweet, User)
             .join(User)
             .group_by(Tweet.user)
             .having(Tweet.created_date == fn.MAX(Tweet.created_date)))

Postgresql requires the standard subquery form above.

  Top N Per Group

These examples describe several ways to query the top N items per group reasonably efficiently. For a thorough discussion of various techniques, check out my blog post Querying the top N objects per group with Peewee ORM <https://charlesleifer.com/blog/querying-the-top-n-objects-per-group-with-peewee-orm/>.

  Window functions

A RANK() window function is the cleanest solution. Rank tweets per user by timestamp (newest first), then filter the outer query to the top N ranks:

    TweetAlias = Tweet.alias()
    ranked = (TweetAlias
              .select(
                  TweetAlias.content,
                  User.username,
                  fn.RANK().over(
                      partition_by=[TweetAlias.user],
                      order_by=[TweetAlias.created_date.desc()]
                  ).alias('rnk'))
              .join(User, on=(TweetAlias.user == User.id))
              .alias('subq'))
    query = (Tweet
             .select(ranked.c.content, ranked.c.username)
             .from_(ranked)
             .where(ranked.c.rnk <= 3))

  Postgresql - lateral joins

A LATERAL join executes a correlated subquery once per row of the driving table. For each user, it selects the three most recent tweets.

The desired SQL is:

    SELECT * FROM
      (SELECT id, username FROM user) AS uq
       LEFT JOIN LATERAL
      (SELECT message, created_date
       FROM tweet
       WHERE (user_id = uq.id)
       ORDER BY created_date DESC LIMIT 3)
      AS pq ON true

To accomplish this with peewee is quite straightforward:

    subq = (Tweet
            .select(Tweet.message, Tweet.created_date)
            .where(Tweet.user == User.id)
            .order_by(Tweet.created_date.desc())
            .limit(3))
    query = (User
             .select(User, subq.c.content, subq.c.created_date)
             .join(subq, JOIN.LEFT_LATERAL)
             .order_by(User.username, subq.c.created_date.desc()))
    # We queried from the "perspective" of user, so the rows are User instances
    # with the addition of a "content" and "created_date" attribute for each of
    # the (up-to) 3 most-recent tweets for each user.
    for row in query:
        print(row.username, row.content, row.created_date)

To implement an equivalent query from the "perspective" of the Tweet model, we can instead write:

    # subq is the same as the above example.
    subq = (Tweet
            .select(Tweet.message, Tweet.created_date)
            .where(Tweet.user == User.id)
            .order_by(Tweet.created_date.desc())
            .limit(3))
    query = (Tweet
             .select(User.username, subq.c.content, subq.c.created_date)
             .from_(User)
             .join(subq, JOIN.LEFT_LATERAL)
             .order_by(User.username, subq.c.created_date.desc()))
    # Each row is a "tweet" instance with an additional "username" attribute.
    # This will print the (up-to) 3 most-recent tweets from each user.
    for tweet in query:
        print(tweet.username, tweet.content, tweet.created_date)

  Correlated subquery count

A correlated subquery that counts tweets newer than the current row can also be used. Rows where fewer than N newer tweets exist are in the top N:

    TweetAlias = Tweet.alias()
    # Create a correlated subquery that calculates the number of
    # tweets with a higher (newer) timestamp than the tweet we're
    # looking at in the outer query.
    subquery = (TweetAlias
                .select(fn.COUNT(TweetAlias.id))
                .where(
                    (TweetAlias.created_date >= Tweet.created_date) &
                    (TweetAlias.user == Tweet.user)))
    # Wrap the subquery and filter on the count.
    query = (Tweet
             .select(Tweet, User)
             .join(User)
             .where(subquery <= 3))

  SQLite and MySQL - self-join

An alternative: self-join and count newer tweets in the HAVING clause:

    TweetAlias = Tweet.alias()
    query = (Tweet
             .select(Tweet.id, Tweet.content, Tweet.user, User.username)
             .join_from(Tweet, User)
             .join_from(Tweet, TweetAlias, on=(
                 (TweetAlias.user == Tweet.user) &
                 (TweetAlias.created_date >= Tweet.created_date)))
             .group_by(Tweet.id, Tweet.content, Tweet.user, User.username)
             .having(fn.COUNT(Tweet.id) <= 3))

The last example uses a LIMIT clause in a correlated subquery.

    TweetAlias = Tweet.alias()
    # The subquery here will calculate, for the user who created the
    # tweet in the outer loop, the three newest tweets. The expression
    # will evaluate to `True` if the outer-loop tweet is in the set of
    # tweets represented by the inner query.
    query = (Tweet
             .select(Tweet, User)
             .join(User)
             .where(Tweet.id << (
                 TweetAlias
                 .select(TweetAlias.id)
                 .where(TweetAlias.user == Tweet.user)
                 .order_by(TweetAlias.created_date.desc())
                 .limit(3))))

For a thorough benchmark comparison of these approaches, see the blog post Querying the top N objects per group with Peewee ORM <https://charlesleifer.com/blog/querying-the-top-n-objects-per-group-with-peewee-orm/>.

  Bulk-Loading with Explicit Primary Keys

When loading relational data from an external source where primary keys are already assigned, use insert_many() <#Model.insert_many> with the id field included. This avoids the auto_increment workaround that was common in older Peewee versions:

    data = [(1, 'alice'), (2, 'bob'), (3, 'carol')]
    fields = [User.id, User.username]
    with db.atomic():
        User.insert_many(data, fields=fields).execute()

Because insert_many never reads rows back, there is no confusion between INSERT and UPDATE paths.

  Custom SQLite Functions

SQLite can be extended with Python functions that are callable from SQL. This is useful for operations SQLite does not natively support.

Registering a function with the @db.func() decorator makes it available immediately after the connection is opened:

    from hashlib import sha256
    import os
    db = SqliteDatabase('my_app.db')
    def _hash_password(salt, password):
        return sha256((salt + password).encode()).hexdigest()
    @db.func()
    def make_password(raw_password):
        salt = os.urandom(8).hex()
        return salt + '$' + _hash_password(salt, raw_password)
    @db.func()
    def check_password(raw_password, stored):
        salt, hsh = stored.split('$', 1)
        return hsh == _hash_password(salt, raw_password)

Store a hashed password:

    User.insert(username='charlie',
                password=fn.make_password('s3cr3t')).execute()

Verify a password at login:

    def login(username, raw_password):
        try:
            return (User
                    .select()
                    .where(
                        (User.username == username) &
                        (fn.check_password(raw_password, User.password) == True))
                    .get())
        except User.DoesNotExist:
            return None

See also: SqliteDatabase.func() <#SqliteDatabase.func>, SqliteDatabase.aggregate() <#SqliteDatabase.aggregate>, SqliteDatabase.window_function() <#SqliteDatabase.window_function>.

  Date Arithmetic Across Databases

Each database implements date arithmetic differently. This section shows how to express "next occurrence of a scheduled task" - defined as last_run + interval_seconds - for each backend.

The schema:

    class Schedule(BaseModel):
        interval = IntegerField()   # Repeat every N seconds.
    class Task(BaseModel):
        schedule = ForeignKeyField(Schedule, backref='tasks')
        command  = TextField()
        last_run = DateTimeField()

We want: tasks where now >= last_run + interval.

Our desired code would look like:

    next_occurrence = something  # ??? how do we define this ???
    # We can express the current time as a Python datetime value, or we could
    # alternatively use the appropriate SQL function/name.
    now = Value(datetime.datetime.now())  # Or SQL('current_timestamp'), e.g.
    query = (Task
             .select(Task, Schedule)
             .join(Schedule)
             .where(now >= next_occurrence))

Postgresql - multiply a typed interval:

    one_second = SQL("INTERVAL '1 second'")
    next_run = Task.last_run + (Schedule.interval * one_second)
    now = Value(datetime.datetime.now())
    tasks_due = (Task
                 .select(Task, Schedule)
                 .join(Schedule)
                 .where(now >= next_run))

MySQL - use DATE_ADD with a dynamic INTERVAL expression:

    from peewee import NodeList
    interval = NodeList((SQL('INTERVAL'), Schedule.interval, SQL('SECOND')))
    next_run = fn.DATE_ADD(Task.last_run, interval)
    now = Value(datetime.datetime.now())
    tasks_due = (Task
                 .select(Task, Schedule)
                 .join(Schedule)
                 .where(now >= next_run))

SQLite - convert to Unix timestamp, add seconds, convert back:

    next_ts = fn.strftime('%s', Task.last_run) + Schedule.interval
    next_run = fn.datetime(next_ts, 'unixepoch')
    now = Value(datetime.datetime.now())
    tasks_due = (Task
                 .select(Task, Schedule)
                 .join(Schedule)
                 .where(now >= next_run))

  Contributing

In order to continually improve, Peewee needs the help of developers like you. Whether it's contributing patches, submitting bug reports, or just asking and answering questions, you are helping to make Peewee a better library.

In this document I'll describe some of the ways you can help.

  Patches

Do you have an idea for a new feature, or is there a clunky API you'd like to improve? Before coding it up and submitting a pull-request, open a new issue <https://github.com/coleifer/peewee/issues/new> on GitHub describing your proposed changes. This doesn't have to be anything formal, just a description of what you'd like to do and why.

When you're ready, you can submit a pull-request with your changes. Successful patches will have the following:

•   Unit tests.

•   Documentation, both prose form and general API documentation <#api>.

•   Code that conforms stylistically with the rest of the Peewee codebase.

  Bugs

If you've found a bug, please check to see if it has already been reported <https://github.com/coleifer/peewee/issues/>, and if not create an issue on GitHub <https://github.com/coleifer/peewee/issues/new>. The more information you include, the more quickly the bug will get fixed, so please try to include the following:

•   Traceback and the error message (please format your code).

•   Relevant portions of your code or code to reproduce the error

•   Peewee version: python -c "from peewee import __version__; print(__version__)"

•   Which database you're using

    If you have found a bug in the code and submit a failing test-case, then hats-off to you, you are a hero!

  Questions

If you have questions about how to do something with peewee, then I recommend either:

•   Ask on StackOverflow. I check SO just about every day for new peewee questions and try to answer them. This has the benefit also of preserving the question and answer for other people to find.

•   Ask on the mailing list, <https://groups.google.com/group/peewee-orm>

NOTE

If you find any bugs, odd behavior, or have an idea for a new feature please don't hesitate to open an issue <https://github.com/coleifer/peewee/issues?state=open> on GitHub.

•   Index <>

•   Module Index <>

•   Search Page <>

Author
charles leifer

Copyright
charles leifer