SQLite

核心的 SqliteDatabase 类处理 pragmas、用户定义函数、WAL 模式、全文搜索和 JSON。由于全文搜索和 JSON 字段是 SQLite 特有的,这些功能由 playhouse.sqlite_ext 提供。

实现

SqliteDatabase

核心 SQLite 实现。提供

  • Pragma 支持(包括 WAL 模式)

  • 用户定义函数

  • ATTACH / DETACH 数据库

  • 全文搜索

  • JSON

全文搜索和 JSON 实现可在 playhouse.sqlite_ext 中找到。

CySqliteDatabaseplayhouse.cysqlite_ext

继承自 SqliteDatabase,使用 cysqlite 驱动。

  • 以上所有功能

  • 表值函数

  • Commit / Rollback / Update / Progress / Trace 钩子

  • BLOB I/O

  • 在线备份

  • 可以 构建为加密版本

APSWDatabaseplayhouse.apsw_ext

继承自 SqliteDatabase,使用 apsw 驱动。

APSW 是一个精简的 C 语言驱动,暴露了 SQLite 的全部功能。

SqlCipherDatabaseplayhouse.sqlcipher_ext

继承自 SqliteDatabase,使用 sqlcipher3 驱动。

SQLCipher 通过 256 位 AES 提供透明的全数据库加密,确保磁盘上的数据安全。

SqliteQueueDatabaseplayhouse.sqliteq

继承自 SqliteDatabase

提供了一个 SQLite 数据库实现,带有一个长期运行的后台写入线程。所有写入操作都由单个写入连接管理,从而防止超时和数据库锁定问题。当在多线程环境中使用 SQLite 进行频繁写入时,此实现非常有用。

PRAGMA 语句

SQLite 允许通过 PRAGMA 语句进行运行时配置(参见 SQLite 文档)。这些语句通常在新数据库连接创建时运行。

指定连接的默认 PRAGMA 语句

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

Pragmas 也可以通过 pragma() 方法或 SqliteDatabase 对象上暴露的特殊属性动态配置。

# 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)

注意

通过 pragma() 方法设置的 Pragmas 在新连接打开时不会重新应用。要配置一个 pragma 以便在每次打开新连接时运行,请指定 permanent=True

db.pragma('foreign_keys', 1, permanent=True)

另请参阅

SQLite PRAGMA 文档:https://sqlite.ac.cn/pragma.html

用户定义函数

SQLite 可以通过用户定义的 Python 代码进行扩展。SqliteDatabase 类支持各种用户定义的扩展:

函数

用户定义函数接受任意数量的参数并返回单个值。

聚合函数

跨多行聚合值并返回单个值。

窗口函数

支持对数据滑动窗口进行操作的聚合函数。

排序规则

控制值如何排序和比较。

表函数

用户定义的表(需要 cysqlite)。

共享库

从共享库加载扩展。

函数示例

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()))

聚合函数示例

用户定义的聚合函数必须定义两个方法:

  • step(*values) - 针对正在聚合的每一行调用一次。

  • finalize() - 只调用一次以产生最终的聚合值。

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))

窗口函数示例

用户定义的窗口函数只是带有两个附加方法的聚合函数:

  • step(*values) - 针对正在聚合的每一行调用一次。

  • inverse(*values) - “反转” `step(*values)` 调用产生的影响。

  • value() - 返回聚合的当前值。

  • finalize() - 返回最终的聚合值。

# 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))

排序规则示例

排序规则接受两个值,并提供一个值来指示它们的排序方式(例如 `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'))

表函数示例

示例用户定义的表值函数(有关 TableFunction 的完整详细信息,请参阅 cysqlite 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

共享库

示例

# Load `closure.so` shared library in the current directory.
db = SqliteDatabase('my_app.db')
db.load_extension('closure')

要支持共享库,您的 SQLite3 必须已编译为支持运行时可加载扩展。

事务的锁定模式

SQLite 事务可以以三种模式打开:

  • 延迟(**默认**)- 仅在执行读或写操作时获取锁。第一次读取会创建一个 共享锁,第一次写入会创建一个 保留锁。由于锁的获取是延迟到实际需要时才进行,因此其他线程或进程有可能创建单独的事务并写入数据库。

  • 立即- 立即获取一个 保留锁。在此模式下,没有其他连接可以写入数据库或打开一个 *立即* 或 *独占* 事务。但是,其他进程可以继续从数据库读取。

  • 独占- 打开一个 独占锁,阻止所有(除了读未提交)连接访问数据库,直到事务完成。

指定锁定模式的示例

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()

有关更多信息,请参阅 SQLite 的 锁定文档。要了解 Peewee 中事务的更多信息,请参阅 事务 文档。

危险

请勿修改 sqlite3.Connection 对象的 `isolation_level` 属性。Peewee 要求 sqlite3 驱动程序处于自动提交模式,这由 SqliteDatabase 自动处理。

CySqlite

CySqliteDatabase 使用 cysqlite 驱动,这是标准库 sqlite3 模块的高性能替代品。cysqlite 提供了标准库 sqlite3 驱动程序中没有的附加功能和钩子。

安装

pip install cysqlite

用法

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)
参数
  • pragmas (list) – 一个 2 元组列表,包含 pragma 键值对,每次打开连接时都会设置。

  • timeout – 设置 SQLite 驱动程序的繁忙超时(以秒为单位)。

  • rank_functions (bool) – 使搜索结果排名函数可用。仅在使用 FTS4 时推荐。

  • regexp_function (bool) – 使 REGEXP 函数可用。

另请参阅

CySqliteDatabase 继承自 SqliteDatabase,并继承了所有用于声明用户定义函数、聚合函数、窗口函数、排序规则、pragma 等的方法。

示例

db = CySqliteDatabase('app.db', pragmas={'journal_mode': 'wal'})
table_function(name)

用于注册 `cysqlite.TableFunction` 的类装饰器。表函数是用户定义的函数,它们不返回单个标量值,而是可以返回任意数量的表格式数据行。

有关 `TableFunction` API 的详细信息,请参阅 cysqlite 文档

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)
参数
  • klass (TableFunction) – 实现 TableFunction API 的类。

  • name (str) – 用户定义表函数的名称。

将 `cysqlite.TableFunction` 类注册到连接。表函数是用户定义的函数,它们不返回单个标量值,而是可以返回任意数量的表格式数据行。

另请参阅

unregister_table_function(name)
参数

name – 用户定义表函数的名称。

返回

如果成功移除函数,则返回 True,否则返回 False。

注销用户定义的表函数。

on_commit(fn)
参数

fn – 可调用对象或 `None` 以清除当前钩子。

注册一个回调函数,该函数将在当前连接提交事务时执行。回调函数不接受任何参数,返回值将被忽略。

但是,如果回调函数引发 `ValueError`,事务将被中止并回滚。

示例

db = CySqliteDatabase(':memory:')

@db.on_commit
def on_commit():
    logger.info('COMMITing changes')
on_rollback(fn)
参数

fn – 可调用对象或 `None` 以清除当前钩子。

注册一个回调函数,该函数将在当前连接回滚事务时执行。回调函数不接受任何参数,返回值将被忽略。

示例

@db.on_rollback
def on_rollback():
    logger.info('Rolling back changes')
on_update(fn)
参数

fn – 可调用对象或 `None` 以清除当前钩子。

注册一个回调函数,该函数将在数据库被写入(通过 `UPDATE`、`INSERT` 或 `DELETE` 查询)时执行。回调函数应接受以下参数:

  • query - 查询类型,即 `INSERT`、`UPDATE` 或 `DELETE`。

  • database name - 默认数据库名为 `main`。

  • table name - 正在修改的表的名称。

  • rowid - 正在修改的行的 `rowid`。

回调函数的返回值将被忽略。

示例

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)
参数

fn – 可调用对象或 `None` 以清除当前授权程序。

注册一个授权程序回调。授权程序回调必须接受 5 个参数,这些参数因要检查的操作而异。

  • op: 操作代码,例如 `cysqlite.SQLITE_INSERT`。

  • p1: 操作特定值,例如 `SQLITE_INSERT` 的表名。

  • p2: 操作特定值。

  • p3: 数据库名称,例如 `"main"`。

  • p4: 访问尝试的内部触发器或视图(如果适用),否则为 `None`。

有关授权程序代码的描述以及参数 p1 和 p2 的值,请参阅 sqlite 授权程序文档

授权程序回调必须返回以下之一:

  • cysqlite.SQLITE_OK: 允许操作。

  • cysqlite.SQLITE_IGNORE: 允许语句编译,但阻止操作发生。

  • cysqlite.SQLITE_DENY: 阻止语句编译。

有关更多详细信息,请参阅 cysqlite 文档

trace(fn, mask=2, expand_sql=True):
参数
  • fn – 可调用对象或 `None` 以清除当前跟踪钩子。

  • mask (int) – 要跟踪的事件类型的掩码。默认值对应于 `SQLITE_TRACE_PROFILE`。

  • expand_sql (bool) – 将 `sqlite3_stmt` 的 `sqlite3_expanded_sql()` 传递给回调(展开绑定参数)。

注册一个跟踪钩子(`sqlite3_trace_v2`)。跟踪回调必须接受 4 个参数,这些参数因要跟踪的操作而异。

  • event: 事件类型,例如 `SQLITE_TRACE_PROFILE`。

  • sid: 语句的内存地址(仅 `SQLITE_TRACE_CLOSE`),否则为 -1。

  • sql: SQL 字符串。如果 `expand_sql` 为 True,则绑定参数将被展开(对于 `SQLITE_TRACE_CLOSE`,`sql=None`)。

  • ns: 语句运行的估计纳秒数(仅 `SQLITE_TRACE_PROFILE`),否则为 -1。

回调返回的任何值都将被忽略。

有关更多详细信息,请参阅 cysqlite 文档

slow_query_log(threshold_ms=50, logger=None, level=logging.WARNING, expand_sql=True)
参数
  • threshold_ms – 记录慢查询的估计毫秒阈值。

  • logger – 日志记录命名空间,默认为 `'peewee.cysqlite_ext'`。

  • level (int) – 慢查询日志的级别。

  • expand_sql (bool) – 在 SQL 查询中展开绑定参数。

注册一个 `sqlite3_trace_v2` 回调,该回调会将慢查询记录到指定的日志记录器。将覆盖先前注册的 `trace()` 回调。在新连接打开时会自动重新注册。

progress(fn, n=1)
参数
  • fn – 可调用对象或 `None` 以清除当前进度处理程序。

  • n (int) – 在调用进度处理程序之间要执行的 VM 指令的大约数量。

注册一个进度处理程序(`sqlite3_progress_handler`)。回调函数不接受任何参数,并返回 0 以允许进度继续,或返回任何非零值以中断进度。

有关更多详细信息,请参阅 cysqlite 文档

autocommit

返回一个布尔值的属性,指示是否启用了自动提交。默认情况下,此值将为 `True`,除非在事务(或 `atomic()` 块)内。

示例

>>> db = CySqliteDatabase(':memory:')
>>> db.autocommit
True
>>> with db.atomic():
...     print(db.autocommit)
...
False
>>> db.autocommit
True
backup(destination, pages=None, name=None, progress=None)
参数
  • destination (CySqliteDatabase) – 将用作备份目标的数据库对象。

  • pages (int) – 每次迭代的页数。默认值 -1 表示所有页面都应在一个步骤中备份。

  • name (str) – 源数据库的名称(如果使用了 ATTACH DATABASE 加载了多个数据库,则可能不同)。默认为 “main”。

  • progress – 进度回调,使用三个参数调用:剩余页数、总页数以及备份是否完成。

示例

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)
参数
  • filename – 用于存储数据库备份的文件名。

  • pages (int) – 每次迭代的页数。默认值 -1 表示所有页面都应在一个步骤中备份。

  • name (str) – 源数据库的名称(如果使用了 ATTACH DATABASE 加载了多个数据库,则可能不同)。默认为 “main”。

  • progress – 进度回调,使用三个参数调用:剩余页数、总页数以及备份是否完成。

将当前数据库备份到文件。备份的数据不是数据库转储,而是实际的 SQLite 数据库文件。

示例

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)
参数
  • table (str) – 包含数据的表的名称。

  • column (str) – 包含数据的列的名称。

  • rowid (int) – 要检索的行的 ID。

  • read_only (bool) – 以只读模式打开 blob。

  • dbname (str) – 数据库名称(例如,如果附加了多个数据库)。

返回

提供高效访问底层二进制数据的 `cysqlite.Blob` 实例。

返回类型

cysqlite.Blob

有关更多详细信息,请参阅 cysqlite 文档

示例

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)

连接池版本的 `CySqliteDatabase`。

APSW

APSW 是 SQLite C API 的一个精简 C 语言包装器,它暴露了几乎所有的 SQLite 功能,包括虚拟表、虚拟文件系统和 BLOB I/O。

安装

pip install apsw

用法

from playhouse.apsw_ext import APSWDatabase

db = APSWDatabase('my_app.db')

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

请使用 `playhouse.apsw_ext` 中的 `Field` 子类,而不是 `peewee` 中的子类,以确保正确的类型适配。例如,使用 `playhouse.apsw_ext.DateTimeField` 而不是 `peewee.DateTimeField`。

class APSWDatabase(database, **connect_kwargs)

使用 APSW 驱动的 `SqliteDatabase` 的子类。

参数
  • database (string) – sqlite 数据库文件名。

  • connect_kwargs – 打开连接时传递给 apsw 的关键字参数。

register_module(mod_name, mod_inst)

全局注册一个虚拟表模块。请参阅 APSW 虚拟表文档

参数
  • mod_name (string) – 要使用的模块名称。

  • mod_inst (object) – 一个实现 `Virtual Table` 接口的对象。

unregister_module(mod_name)

注销之前注册的模块。

SQLCipher

SQLCipher 是 SQLite 的一个加密包装器。Peewee 通过 SqlCipherDatabase 暴露它,该类与 `SqliteDatabase` 的 API 相同,但构造函数除外。

安装

pip install sqlcipher3

用法

from playhouse.sqlcipher_ext import SqlCipherDatabase

db = SqlCipherDatabase(
    'app.db',
    passphrase=os.environ['PASSPHRASE'],
    pragmas={'cache_size': -64000})

使用延迟初始化和密码提示的示例用法

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 配置(例如,增加 PBKDF2 迭代次数)

db = SqlCipherDatabase('my_app.db',
                       passphrase='s3cr3t',
                       pragmas={'kdf_iter': 1_000_000})

SQLCipher 可以使用许多扩展 PRAGMA 进行配置。PRAGMA 列表及其描述可在 SQLCipher 文档 中找到。

class SqlCipherDatabase(database, passphrase, **kwargs)
参数
  • database (str) – 加密数据库文件的路径。

  • passphrase (str) – 加密密码(至少 8 个字符;在您的应用程序中强制要求更强的要求)。

如果数据库文件不存在,则会创建它并使用从 `passphrase` 派生的密钥进行加密。如果文件已存在,则 `passphrase` 必须与创建文件时使用的密码匹配。

如果密码不正确,在第一次尝试访问数据库时会引发错误(通常是 `DatabaseError: file is not a database`)。

rekey(passphrase)

更改打开数据库的加密密码。

SqliteQueueDatabase

SqliteQueueDatabase 将所有写入查询通过一个长期运行的连接在一个专用的后台线程中串行化。这允许多个应用程序线程并发地写入 SQLite 数据库,而不会发生冲突或超时错误。

如果您想从多个线程对 SQLite 数据库进行简单的 **读写** 访问,并且不需要事务,可以将 `SqliteQueueDatabase` 用作普通 `SqliteDatabase` 的即插即用替换。

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'})

如果设置 `autostart=False`,则显式启动写入器线程

db.start()

在应用程序关闭时停止写入器线程(等待待处理写入完成)

import atexit

@atexit.register
def _stop():
    db.stop()

读取查询正常工作。按需打开和关闭连接,就像使用任何其他数据库一样。只有写入操作会通过队列进行。

**不支持事务。** 因为不同线程的写入操作是交错的,所以无法保证一个线程的事务中的语句会原子地执行,而不会出现另一个线程的语句插入其中。如果调用 `atomic()` 和 `transaction()` 方法,将引发 `ValueError`。

如果您需要暂时绕过队列并直接写入(例如,在批量导入期间),请使用 `pause()` 和 `unpause()`。

class SqliteQueueDatabase(database, use_gevent=False, autostart=True, queue_max_size=None, results_timeout=None, **kwargs)
参数
  • database (str) – 数据库文件名。

  • use_gevent (bool) – 使用 gevent 而不是 `threading`。

  • autostart (bool) – 自动启动后台写入线程。

  • queue_max_size (int) – 待处理写入队列的最大尺寸。

  • results_timeout (float) – 等待写入线程返回查询结果的超时时间(秒)。

start()

启动后台写入线程。

stop()

向写入线程发出停止信号。阻塞直到所有待处理写入都完成。

is_stopped()

如果写入线程未运行,则返回 `True`。

pause()

阻塞直到写入线程完成当前工作,然后断开其连接。调用线程将接管直接数据库访问。必须随后调用 `unpause()`。

unpause()

恢复写入线程并重新连接队列。

SQLite 特有字段

这些字段类位于 `playhouse.sqlite_ext` 中,可与

class RowIDField

映射到 SQLite 隐式 `rowid` 列的主键字段。

有关更多信息,请参阅 SQLite 关于 rowid 表 的文档。

class Note(Model):
    rowid = RowIDField()  # Implied primary_key=True.
    content = TextField()
    timestamp = TimestampField()

RowIDField 可以映射到不同的字段名,但其底层列名始终是 `rowid`。

class Note(Model):
    id = RowIDField()
    ...
class AutoIncrementField

整数主键,使用 SQLite 的 `AUTOINCREMENT` 关键字,保证主键即使在删除后也始终严格递增。与默认的 `PrimaryKeyField` 或 `RowIDField` 相比,性能略有下降。

有关详细信息,请参阅 SQLite AUTOINCREMENT 文档

class ISODateTimeField

DateTimeField 的子类,在存储到 SQLite 的基于文本的日期时间表示时,保留时区感知日期时间对象的 UTC 偏移信息。

class TDecimalField(max_digits=10, decimal_places=5, auto_round=False, rounding=None, *args, **kwargs)

DecimalField 的子类,它将 decimal 值存储在 `TEXT` 列中,以避免在存储到 `REAL`(双精度浮点)列时可能发生的任何精度损失。SQLite 没有真正的数字类型,因此该字段确保在使用 Decimal 时不会丢失精度。

SQLite JSON

JSONField 使用 SQLite json 函数 实现对 JSON 数据的存储和查询。

class JSONField(json_dumps=None, json_loads=None, **kwargs)
参数
  • json_dumps – 自定义 JSON 序列化器。默认为 `json.dumps`。

  • json_loads – 自定义 JSON 反序列化器。默认为 `json.loads`。

透明地存储和检索 JSON 数据,并为就地修改和查询提供高效的实现。数据在写入时自动序列化,在读取时反序列化。

示例模型

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}})

要访问或修改 JSON 结构中的特定对象键或数组索引,您可以像对待字典/列表一样对待 `JSONField`:

# 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)

数据可以就地原子地更新、写入和删除。

# 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())

其他 JSON 场景的助手

# 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)]

让我们添加一个嵌套值,然后看看如何使用 `tree()` 方法递归地遍历其内容。

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')]

`tree()` 和 `children()` 方法非常强大。有关如何使用它们的更多信息,请参阅 SQLite 的 json1 扩展文档

__getitem__(item)
参数

item – 访问 JSON 数据中的特定键或数组索引。

返回

一个特殊的显式访问 JSON 数据的对象。

返回类型

JSONPath

访问 JSON 数据中的特定键或数组索引。返回一个 `JSONPath` 对象,该对象暴露了用于读取或修改 JSON 对象特定部分的便捷方法。

示例

# 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'))

更多示例请参阅 JSONPath API 文档。

extract(*paths)
参数

paths – 一个或多个要提取的 JSON 路径。

提取一个或多个 JSON 路径值。当提供多个路径时,返回一个列表。

extract_json(path)
参数

path (str) – JSON 路径。

以 JSON 数据类型提取指定路径的值。这对应于 SQLite 3.38 中添加的 `->` 运算符。

extract_text(path)
参数

path (str) – JSON 路径。

以 SQL 数据类型提取指定路径的值。这对应于 SQLite 3.38 中添加的 `->>` 运算符。

set(value, as_json=None)
参数
  • value – 标量值、列表或字典。

  • as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。

设置存储在 `JSONField` 中的值。

使用 json1 扩展的 `json_set()` 函数。

replace(value, as_json=None)
参数
  • value – 标量值、列表或字典。

  • as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。

替换存储在 `JSONField` 中的现有值。如果不存在,则不会创建。

使用 json1 扩展的 `json_replace()` 函数。

insert(value, as_json=None)
参数
  • value – 标量值、列表或字典。

  • as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。

将值插入 `JSONField`。如果已存在,则不会覆盖。

使用 json1 扩展的 `json_insert()` 函数。

append(value, as_json=None)
参数
  • value – 标量值、列表或字典。

  • as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。

将值附加到存储在 `JSONField` 中的数组。

使用 json1 扩展的 `json_set()` 函数。

update(data)
参数

data – 一个标量值、列表或字典,用于与 `JSONField` 中当前存储的数据合并。要删除某个键,请将其在更新的数据中设置为 `None`。

使用 RFC-7396 MergePatch 算法将补丁(`data` 参数)应用于列数据,从而将新数据合并到 JSON 值中。MergePatch 可以添加、修改或删除 JSON 对象的元素,这意味着 `update()` 是 `set()` 和 `remove()` 的通用替代方法。MergePatch 将 JSON 数组对象视为原子单元,因此 `update()` 无法向数组追加元素,也无法修改数组的单个元素。

有关更多信息和示例,请参阅 SQLite 的 json_patch() 函数文档。

remove()

删除存储在 `JSONField` 中的数据。

使用 json1 扩展的 `json_remove` 函数。

json_type()

返回一个字符串,标识存储在列中的值的类型。

返回的类型将是以下之一:

  • object

  • array

  • integer

  • real

  • true

  • false

  • text

  • null – 字符串 “null” 表示实际的 NULL 值

  • NULL – 实际的 NULL 值表示路径未找到

使用 json1 扩展的 `json_type` 函数。

length()

返回存储在列中的数组的长度。

使用 json1 扩展的 `json_array_length` 函数。

children()

`children` 函数对应于 `json_each`,这是一个表值函数,它遍历提供的 JSON 值并返回顶级数组或对象的直接子项。如果指定了路径,则该路径被视为最顶层的元素。

调用 `children()` 返回的行具有以下属性:

  • key: 当前元素相对于其父级的键。

  • value: 当前元素的值。

  • type: 数据类型之一(请参阅 `json_type()`)。

  • atom: 原始类型的值,数组和对象的 `NULL`。

  • id: 引用树中当前节点的唯一 ID。

  • parent: 包含节点的 ID。

  • fullkey: 描述当前元素的完整路径。

  • path: 当前行的容器的路径。

此方法在内部使用 json1 扩展的 `json_each`(文档链接)函数。

用法示例(与 `tree()` 方法比较)

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()

`tree` 函数对应于 `json_tree`,这是一个表值函数,它递归地遍历提供的 JSON 值并返回每个级别键的信息。如果指定了路径,则该路径被视为最顶层的元素。

调用 `tree()` 返回的行具有与调用 `children()` 返回的行相同的属性。

  • key: 当前元素相对于其父级的键。

  • value: 当前元素的值。

  • type: 数据类型之一(请参阅 `json_type()`)。

  • atom: 原始类型的值,数组和对象的 `NULL`。

  • id: 引用树中当前节点的唯一 ID。

  • parent: 包含节点的 ID。

  • fullkey: 描述当前元素的完整路径。

  • path: 当前行的容器的路径。

此方法在内部使用 json1 扩展的 `json_tree`(文档链接)函数。

使用示例

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)
参数
  • field (JSONField) – 我们打算访问的字段对象。

  • path (tuple) – 组成 JSON 路径的组件。

一种便捷的 Pythonic 表示 JSON 路径的方式,用于 `JSONField`。它实现了与 `JSONField` 相同的接口,但专为操作嵌套项而设计,例如:

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)

扩展自 JSONField,并将数据存储为二进制 jsonb 格式(SQLite 3.45.0+)。读取原始列值时,数据是其编码的二进制形式,请使用 json() 方法进行解码。

# 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()

指示 JSONB 字段数据应被反序列化并作为 JSON 返回(而不是 SQLite 二进制格式)。

用户定义的函数集合

playhouse.sqlite_udf 包含一组函数、聚合和表值函数,这些函数按名称分组到集合中。

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')

注册后,通过 Peewee 的 fn 命名空间或原始 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())

可用函数

CONTROL_FLOW

if_then_else(cond, truthy, falsey=None)

简单的三元运算符,根据 cond 参数的真值,返回 truthyfalsey 值。

DATE

strip_tz(date_str)
参数

date_str – 一个日期时间,编码为字符串。

返回

带有时区信息被剥离的日期时间。

时间不会以任何方式调整,只是移除了时区。

humandelta(nseconds, glue=', ')
参数
  • nseconds (int) – 总秒数,在 timedelta 中。

  • glue (str) – 连接值的片段。

返回

易于阅读的 timedelta 描述。

例如,86471 -> “1 天, 1 分钟, 11 秒”

mintdiff(datetime_value)
参数

datetime_value – 一个日期时间。

返回

列表中任何两个值之间的最小差值。

聚合:任意两个日期时间之间的最小差值。

avgtdiff(datetime_value)
参数

datetime_value – 一个日期时间。

返回

列表中值之间的平均差值。

聚合:连续值之间的平均差值。

duration(datetime_value)
参数

datetime_value – 一个日期时间。

返回

列表中从最小到最大的值之间的持续时间,以秒为单位。

聚合:从最小值到最大值的持续时间,以秒为单位。

date_series(start, stop, step_seconds=86400)
参数
  • start (datetime) – 开始日期时间

  • stop (datetime) – 停止日期时间

  • step_seconds (int) – 构成一步的秒数。

表值函数:返回由从开始到停止迭代遇到的日期/时间值组成的行,step_seconds 一次。

此外,如果开始时间没有时间部分且 step_seconds 大于或等于一天(86400 秒),则返回的值将是日期。反之,如果开始时间没有日期部分,则返回值将是时间。否则,返回值将是日期时间。

示例

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)
参数

filename (str) – 要从中提取扩展名的文件名。

返回

返回文件扩展名,包括开头的“.”。

file_read(filename)
参数

filename (str) – 要读取的文件名。

返回

文件内容。

HELPER

gzip(data, compression=9)
参数
  • data (bytes) – 要压缩的数据。

  • compression (int) – 压缩级别(9 为最高)。

返回

压缩后的二进制数据。

gunzip(data)
参数

data (bytes) – 压缩数据。

返回

未压缩的二进制数据。

hostname(url)
参数

url (str) – 要从中提取主机名的 URL。

返回

URL 的主机名部分。

toggle(key)
参数

key – 要切换的键。

在 True/False 状态之间切换键。例如:

>>> toggle('my-key')
True
>>> toggle('my-key')
False
>>> toggle('my-key')
True
setting(key, value=None)
参数
  • key – 要设置/检索的键。

  • value – 要设置的值。

返回

与键关联的值。

在应用程序的生命周期内存储/检索内存中的设置并使其持久化。要获取当前值,请指定键。要设置新值,请使用键和新值调用。

clear_toggles()

清除与 toggle() 函数关联的所有状态。

clear_settings()

清除与 setting() 函数关联的所有状态。

MATH

randomrange(start, stop=None, step=None)
参数
  • start (int) – 范围的开始(包含)

  • end (int) – 范围的结束(不包含)

  • step (int) – 返回值的间隔。

返回一个介于 [start, end) 之间的随机整数。

gauss_distribution(mean, sigma)
参数
  • mean (float) – 平均值

  • sigma (float) – 标准差

sqrt(n)

计算 n 的平方根。

tonumber(s)
参数

s (str) – 要转换为数字的字符串。

返回

整数、浮点数或在失败时为 NULL。

mode(val)
参数

val – 列表中的数字。

返回

出现次数最多(最常见)的数字。

聚合:计算值的*众数*。

minrange(val)
参数

val – 值

返回

两个值之间的最小差值。

聚合:序列中两个数字之间的最小距离。

avgrange(val)
参数

val – 值

返回

值之间的平均差值。

聚合:序列中连续数字之间的平均距离。

range(val)
参数

val – 值

返回

序列中从最小值到最大值的范围。

聚合:观察到的值的范围。

median(val)
参数

val – 值

返回

序列中的中位数(中间)值。

聚合:序列的中位数。

注意

仅在编译了 _sqlite_udf 扩展时可用。

STRING

substr_count(haystack, needle)

返回 needlehaystack 中出现的次数。

strip_chars(haystack, chars)

haystack 的开头和结尾剥离 chars 中的任何字符。

damerau_levenshtein_dist(s1, s2)

使用 Levenshtein 算法的 Damerau 变体计算从 s1 到 s2 的编辑距离。

注意

仅在编译了 _sqlite_udf 扩展时可用。

levenshtein_dist(s1, s2)

使用 Levenshtein 算法计算从 s1 到 s2 的编辑距离。

注意

仅在编译了 _sqlite_udf 扩展时可用。

str_dist(s1, s2)

使用标准库 SequenceMatcher 的算法计算从 s1 到 s2 的编辑距离。

注意

仅在编译了 _sqlite_udf 扩展时可用。

参数
  • regex (str) – 正则表达式

  • search_string (str) – 要搜索 regex 匹配项的字符串。

表值函数:在字符串中搜索与提供的 regex 匹配的子字符串。返回找到的每个匹配项的行。

示例

SELECT * FROM regex_search('\w+', 'extract words, ignore! symbols');

value
-----
extract
words
ignore
symbols