Using SQLite

To connect to a SQLite database, we will use SqliteDatabase. The first parameter is the filename containing the database, or the string ':memory:' to create an in-memory database. After the database filename, you can specify a list or pragmas or any other arbitrary sqlite3 parameters.

  1. sqlite_db = SqliteDatabase('my_app.db', pragmas={'journal_mode': 'wal'})
  2. class BaseModel(Model):
  3. """A base model that will use our Sqlite database."""
  4. class Meta:
  5. database = sqlite_db
  6. class User(BaseModel):
  7. username = TextField()
  8. # etc, etc

Peewee includes a SQLite extension module which provides many SQLite-specific features such as full-text search, json extension support, and much, much more. If you would like to use these awesome features, use the SqliteExtDatabase from the playhouse.sqlite_ext module:

  1. from playhouse.sqlite_ext import SqliteExtDatabase
  2. sqlite_db = SqliteExtDatabase('my_app.db', pragmas={
  3. 'journal_mode': 'wal', # WAL-mode.
  4. 'cache_size': -64 * 1000, # 64MB cache.
  5. 'synchronous': 0}) # Let the OS manage syncing.

PRAGMA statements

SQLite allows run-time configuration of a number of parameters through PRAGMA statements (SQLite documentation). These statements are typically run when a new database connection is created. To run one or more PRAGMA statements against new connections, you can specify them as a dictionary or a list of 2-tuples containing the pragma name and value:

  1. db = SqliteDatabase('my_app.db', pragmas={
  2. 'journal_mode': 'wal',
  3. 'cache_size': 10000, # 10000 pages, or ~40MB
  4. 'foreign_keys': 1, # Enforce foreign-key constraints
  5. })

PRAGMAs may also be configured dynamically using either the pragma() method or the special properties exposed on the SqliteDatabase object:

  1. # Set cache size to 64MB for *current connection*.
  2. db.pragma('cache_size', -1024 * 64)
  3. # Same as above.
  4. db.cache_size = -1024 * 64
  5. # Read the value of several pragmas:
  6. print('cache_size:', db.cache_size)
  7. print('foreign_keys:', db.foreign_keys)
  8. print('journal_mode:', db.journal_mode)
  9. print('page_size:', db.page_size)
  10. # Set foreign_keys pragma on current connection *AND* on all
  11. # connections opened subsequently.
  12. db.pragma('foreign_keys', 1, permanent=True)

Attention

Pragmas set using the pragma() method, by default, do not persist after the connection is closed. To configure a pragma to be run whenever a connection is opened, specify permanent=True.

Note

A full list of PRAGMA settings, their meaning and accepted values can be found in the SQLite documentation: http://sqlite.org/pragma.html

Recommended Settings

The following settings are what I use with SQLite for a typical web application database.

pragmarecommended settingexplanation
journal_modewalallow readers and writers to co-exist
cache_size-1 * data_size_kbset page-cache size in KiB, e.g. -32000 = 32MB
foreign_keys1enforce foreign-key constraints
ignore_check_constraints0enforce CHECK constraints
synchronous0let OS handle fsync (use with caution)

Example database using the above options:

  1. db = SqliteDatabase('my_app.db', pragmas={
  2. 'journal_mode': 'wal',
  3. 'cache_size': -1 * 64000, # 64MB
  4. 'foreign_keys': 1,
  5. 'ignore_check_constraints': 0,
  6. 'synchronous': 0})

User-defined functions

SQLite can be extended with user-defined Python code. The SqliteDatabase class supports three types of user-defined extensions:

  • Functions - which take any number of parameters and return a single value.
  • Aggregates - which aggregate parameters from multiple rows and return a single value.
  • Collations - which describe how to sort some value.

Note

For even more extension support, see SqliteExtDatabase, which is in the playhouse.sqlite_ext module.

Example user-defined function:

  1. db = SqliteDatabase('analytics.db')
  2. from urllib.parse import urlparse
  3. @db.func('hostname')
  4. def hostname(url):
  5. if url is not None:
  6. return urlparse(url).netloc
  7. # Call this function in our code:
  8. # The following finds the most common hostnames of referrers by count:
  9. query = (PageView
  10. .select(fn.hostname(PageView.referrer), fn.COUNT(PageView.id))
  11. .group_by(fn.hostname(PageView.referrer))
  12. .order_by(fn.COUNT(PageView.id).desc()))

Example user-defined aggregate:

  1. from hashlib import md5
  2. @db.aggregate('md5')
  3. class MD5Checksum(object):
  4. def __init__(self):
  5. self.checksum = md5()
  6. def step(self, value):
  7. self.checksum.update(value.encode('utf-8'))
  8. def finalize(self):
  9. return self.checksum.hexdigest()
  10. # Usage:
  11. # The following computes an aggregate MD5 checksum for files broken
  12. # up into chunks and stored in the database.
  13. query = (FileChunk
  14. .select(FileChunk.filename, fn.MD5(FileChunk.data))
  15. .group_by(FileChunk.filename)
  16. .order_by(FileChunk.filename, FileChunk.sequence))

Example collation:

  1. @db.collation('ireverse')
  2. def collate_reverse(s1, s2):
  3. # Case-insensitive reverse.
  4. s1, s2 = s1.lower(), s2.lower()
  5. return (s1 < s2) - (s1 > s2) # Equivalent to -cmp(s1, s2)
  6. # To use this collation to sort books in reverse order...
  7. Book.select().order_by(collate_reverse.collation(Book.title))
  8. # Or...
  9. Book.select().order_by(Book.title.asc(collation='reverse'))

Example user-defined table-value function (see TableFunction and table_function) for additional details:

  1. from playhouse.sqlite_ext import TableFunction
  2. db = SqliteDatabase('my_app.db')
  3. @db.table_function('series')
  4. class Series(TableFunction):
  5. columns = ['value']
  6. params = ['start', 'stop', 'step']
  7. def initialize(self, start=0, stop=None, step=1):
  8. """
  9. Table-functions declare an initialize() method, which is
  10. called with whatever arguments the user has called the
  11. function with.
  12. """
  13. self.start = self.current = start
  14. self.stop = stop or float('Inf')
  15. self.step = step
  16. def iterate(self, idx):
  17. """
  18. Iterate is called repeatedly by the SQLite database engine
  19. until the required number of rows has been read **or** the
  20. function raises a `StopIteration` signalling no more rows
  21. are available.
  22. """
  23. if self.current > self.stop:
  24. raise StopIteration
  25. ret, self.current = self.current, self.current + self.step
  26. return (ret,)
  27. # Usage:
  28. cursor = db.execute_sql('SELECT * FROM series(?, ?, ?)', (0, 5, 2))
  29. for value, in cursor:
  30. print(value)
  31. # Prints:
  32. # 0
  33. # 2
  34. # 4

For more information, see:

Set locking mode for transaction

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 and the first write creates a reserved lock. 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 after the BEGIN on the current thread has executed.
  • Immediate - a reserved lock is acquired immediately. In this mode, no other database 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 which prevents all (except for read uncommitted) connections from accessing the database until the transaction is complete.

Example specifying the locking mode:

  1. db = SqliteDatabase('app.db')
  2. with db.atomic('EXCLUSIVE'):
  3. do_something()
  4. @db.atomic('IMMEDIATE')
  5. def some_other_function():
  6. # This function is wrapped in an "IMMEDIATE" transaction.
  7. do_something_else()

For more information, see the SQLite locking documentation. To learn more about transactions in Peewee, see the Managing Transactions documentation.

APSW, an Advanced SQLite Driver

Peewee also comes with an alternate SQLite database that uses apsw, an advanced sqlite driver, an advanced Python SQLite driver. More information on APSW can be obtained on the APSW project website. APSW provides special features like:

  • Virtual tables, virtual file-systems, Blob I/O, backups and file control.
  • Connections can be shared across threads without any additional locking.
  • Transactions are managed explicitly by your code.
  • Unicode is handled correctly.
  • APSW is faster that the standard library sqlite3 module.
  • Exposes pretty much the entire SQLite C API to your Python app.

If you would like to use APSW, use the APSWDatabase from the apsw_ext module:

  1. from playhouse.apsw_ext import APSWDatabase
  2. apsw_db = APSWDatabase('my_app.db')