Using SQLite

To connect to a SQLite database, we will use SqliteDatabase. Thefirst 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.  
  3. class BaseModel(Model):
  4. """A base model that will use our Sqlite database."""
  5. class Meta:
  6. database = sqlite_db
  7.  
  8. class User(BaseModel):
  9. username = TextField()
  10. # etc, etc

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

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

PRAGMA statements

SQLite allows run-time configuration of a number of parameters throughPRAGMA 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 canspecify them as a dictionary or a list of 2-tuples containing the pragma nameand 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 thepragma() method or the special properties exposed onthe SqliteDatabase object:

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

Note

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

The following settings are what I use with SQLite for a typical webapplication 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. TheSqliteDatabase class supports three types of user-definedextensions:

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

Note

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

Example user-defined function:

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

Example user-defined aggregate:

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

Example collation:

  1. @db.collation('ireverse')def collate_reverse(s1, s2):

  2. # Case-insensitive reverse.
  3. s1, s2 = s1.lower(), s2.lower()
  4. return (s1 < s2) - (s1 > s2)  # Equivalent to -cmp(s1, s2)
  5. To use this collation to sort books in reverse order

    Book.select().order_by(collate_reverse.collation(Book.title))

  6. Or

    Book.select().order_by(Book.title.asc(collation='reverse'))

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

  1. from playhouse.sqlite_ext import TableFunction
  2.  
  3. db = SqliteDatabase('my_app.db')
  4.  
  5. @db.table_function('series')
  6. class Series(TableFunction):
  7. columns = ['value']
  8. params = ['start', 'stop', 'step']
  9.  
  10. def initialize(self, start=0, stop=None, step=1):
  11. """
  12. Table-functions declare an initialize() method, which is
  13. called with whatever arguments the user has called the
  14. function with.
  15. """
  16. self.start = self.current = start
  17. self.stop = stop or float('Inf')
  18. self.step = step
  19.  
  20. def iterate(self, idx):
  21. """
  22. Iterate is called repeatedly by the SQLite database engine
  23. until the required number of rows has been read **or** the
  24. function raises a `StopIteration` signalling no more rows
  25. are available.
  26. """
  27. if self.current > self.stop:
  28. raise StopIteration
  29.  
  30. ret, self.current = self.current, self.current + self.step
  31. return (ret,)
  32.  
  33. # Usage:
  34. cursor = db.execute_sql('SELECT * FROM series(?, ?, ?)', (0, 5, 2))
  35. for value, in cursor:
  36. print(value)
  37.  
  38. # Prints:
  39. # 0
  40. # 2
  41. # 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 isperformed. The first read creates a shared lockand the first write creates a reserved lock.Because the acquisition of the lock is deferred until actually needed, it ispossible that another thread or process could create a separate transactionand write to the database after the BEGIN on the current thread has executed.
  • Immediate - a reserved lockis acquired immediately. In this mode, no other database may write to thedatabase or open an immediate or exclusive transaction. Other processescan continue to read from the database, however.
  • Exclusive - opens an exclusive lockwhich prevents all (except for read uncommitted) connections from accessingthe database until the transaction is complete.

Example specifying the locking mode:

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

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

APSW, an Advanced SQLite Driver

Peewee also comes with an alternate SQLite database that uses apsw, an advanced sqlite driver, anadvanced Python SQLite driver. More information on APSW can be obtained on theAPSW project website. APSW providesspecial 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 theapsw_ext module:

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